def proc_run(self, command, stdin_str="", timeout=10, outputfun=None): # Timeout is only used with outputfun self.debug("run '" + str(" ".join(command)) + "', input len=" + str(len(stdin_str))) try: if outputfun: return self.proc_run_outputfun(command, timeout, outputfun) else: proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdoutdata, stderrdata) = proc.communicate(input=stdin_str) self.debug("run finished, output len=" + str(len(stdoutdata)) + ", err len=" + str(len(stderrdata))) return (stdoutdata, stderrdata) except OSError as oserr: if oserr.errno == 2: # File not found raise ActionError({ 'error': "Dependent application not found, please install: " + str(command[0]) }) raise ActionError( {'error': "OSError when executing " + str(command[0])}) except: self.debug("run failed:\n" + repr(traceback.format_exception(*sys.exc_info()))) raise ActionError( {'error': "Failed to execute " + str(command[0])})
def get_conn_details(self): def safe_ncs_decrypt(value): if value is None: return None try: return _ncs.decrypt(len=255, ciphertext=value) except: return _ncs.decrypt(ciphertext=value) with single_trans(_ncs.READ_WRITE) as t: device_path = '/ncs:devices/device{"' + self.dev_name + '"}' address = str(t.get_elem(device_path + '/address')) device_type_netconf = t.exists(device_path + '/device-type/netconf') try: port = int(t.get_elem(device_path + '/port')) except: port = 830 self.debug("Device addr={0}, port={1}, netconf={2}".format( address, port, device_type_netconf)) if device_type_netconf != True: raise ActionError( {'error': "pioneer only works with NETCONF devices"}) authgroup_name = str(t.get_elem(device_path + '/authgroup')) ## FIXME default-map only, add umap support -- or at least an error message authgroup_path = '/ncs:devices/authgroups/group{"' + authgroup_name + '"}/default-map' missing = [] try: remote_name = str(t.get_elem(authgroup_path + '/remote-name')) except: missing.append('remote-name') try: remote_password = str( t.get_elem(authgroup_path + '/remote-password')) # FIXME password only except: missing.append('remote-password') if len(missing) > 0: raise ActionError({ 'error': 'required configuration in authentication group {0} default-map missing: {1}' .format(authgroup_name, ', '.join(missing)) }) self.debug("Credentials user={0}, pass={1}".format( remote_name, remote_password)) remote_password = safe_ncs_decrypt(remote_password) return (address, port, remote_name, remote_password)
def create_yang_dir(self): try: os.makedirs(self.yang_directory) except: pass if not os.path.exists(self.yang_directory): raise ActionError({'error': 'Failed to create directory {0}'.format(self.yang_directory)})
def transition_to_state(self, to_state_filename): filename = os.path.join(self.states_dir, to_state_filename) if not os.path.exists(filename): state_name = self.state_filename_to_name(to_state_filename, self.dev_name) raise ActionError( {'error': 'No such state: {0}'.format(state_name)}) try: self.debug("Transition_to_state: #{0}\n".format(filename)) self.extend_timeout( 1200 ) # Max 1200 seconds for executing the transaction and a compare-config thandle = maapi.start_trans2(self.msocket, _ncs.RUNNING, _ncs.READ_WRITE, self.uinfo.usid) maapi.delete(self.msocket, thandle, "/ncs:devices/device{" + self.dev_name + "}/config") maapi.load_config(self.msocket, thandle, maapi.CONFIG_J + maapi.CONFIG_MERGE, filename) maapi.apply_trans(self.msocket, thandle, False) self.debug("Committed\n") result = maapi.request_action( self.msocket, [], 0, "/ncs:devices/device{" + self.dev_name + "}/compare-config") if [] == result: self.debug("In sync\n") return True else: return "out-of-sync" except: self.debug("Exception: " + repr(traceback.format_exception(*sys.exc_info()))) return "transaction-failed" finally: maapi.finish_trans(self.msocket, thandle)
def fetch_model_list_netconf_monitoring(self, method): if method == 'subtree': self.debug( "Fetching YANG model list with netconf-console --get --subtree from netconf-monitoring" ) xml_get_result = self.nc_perform( subtree= "<netconf-state xmlns='urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring'/>" ) else: self.debug( "Fetching YANG model list with netconf-console --get --xpath from netconf-monitoring" ) xml_get_result = self.nc_perform(xpath="/netconf-state") self.debug("Parsing model names") (model_list_txt, stderr) = self.proc_run([ self.get_exe_path("xsltproc"), "--nonet", "--novalid", self.pkg_root_dir + "/load-dir/ncs-extract-schemas.xsl", "-" ], xml_get_result) self.debug("Parsed:\n" + model_list_txt + "\n" + stderr) if stderr != "": raise ActionError( {'error': "Failed to parse model list:\n" + stderr}) model_list = [] lines = [line for line in model_list_txt.split("\n") if line != ""] for line in lines: tokens = line.split(":") self.debug("tokens=" + str(tokens)) if tokens[0] == 'netconf': model_list += [tokens[1]] else: model_list += [tokens[0]] return model_list
def nc_perform(self, op='get', subtree='', xpath='', method_opts=None, timeout=20): if subtree != '': method_opts = ["--subtree", subtree] elif xpath != '': method_opts = ["--xpath", xpath] elif method_opts is None: method_opts = [] (address, port, remote_name, remote_password) = self.get_conn_details() args = ["--" + op] + method_opts + [ "--host=" + str(address), "--port=" + str(port), "--user="******"--password="******"Calling netconf_console %s" % args) netconf_console.main(args, iocb, self) self.debug("Returned from netconf_console") xml_get_result = iocb.out.getvalue() stderr = iocb.err.getvalue() self.debug("Fetched:\n" + xml_get_result + "\n\n" + stderr) if stderr != "": raise ActionError( {'error': "Failed to execute " + op + ":\n" + stderr}) return xml_get_result
def get_exe_path(self, exe): path = self.get_exe_path_from_PATH(exe) if not os.path.exists(path): raise ActionError({ 'error': 'Unable to execute {0}, command no found in PATH {1}'.format( exe, os.environ['PATH']) }) return path
def extract_capas_from_hello(self, hello_str): self.debug("Parsing capas") (capas_list_txt, stderr) = self.proc_run([ self.get_exe_path("xsltproc"), "--nonet", "--novalid", self.pkg_root_dir + "/load-dir/ncs-extract-capas.xsl", "-" ], hello_str) self.debug("Parsed:\n" + capas_list_txt + "\n" + stderr) if stderr != "": raise ActionError( {'error': "Failed to parse capas list:\n" + stderr}) capas_list = capas_list_txt.split("\n") return capas_list
def _yang_sftp_read_settings(self): def safe_get(sock, th, path, default=None): try: return maapi.get_elem(sock, th, path) except _ncs.error.Error as e: if e.confd_errno == _ncs.ERR_NOEXISTS: if isinstance(default, Exception): raise default return default else: raise e sock = socket.socket() maapi.connect(sock, '127.0.0.1', _ncs.PORT) try: maapi.start_user_session2(sock, 'admin', 'system', [], '127.0.0.1', 0, _ncs.PROTO_TCP) try: th = maapi.start_trans(sock, _ncs.RUNNING, _ncs.READ) sftp_prefix = '/{0}:{1}/{2}/'.format( ns.ns.prefix, ns.ns.pioneer_pioneer_, ns.ns.pioneer_sftp_) host_path = sftp_prefix + ns.ns.pioneer_host_ host = safe_get(sock, th, host_path, ActionError({'error': host_path + ' is required'})) port = safe_get(sock, th, sftp_prefix + ns.ns.pioneer_port_, 22) username_path = sftp_prefix + ns.ns.pioneer_username_ username = safe_get(sock, th, username_path, ActionError({'error': username_path + ' is required'})) password = safe_get(sock, th, sftp_prefix + ns.ns.pioneer_password_, None) rsa_key_path = safe_get(sock, th, sftp_prefix + ns.ns.pioneer_rsa_key_path_, '~/id_rsa') return (str(host), int(port), str(username), password and str(password) or None, os.path.expanduser(str(rsa_key_path))) finally: maapi.end_user_session(sock) finally: sock.close()
def perform(self): self.debug("yang_sftp() with device {0}".format(self.dev_name)) self.create_yang_dir() try: import paramiko except ImportError: raise ActionError({'error':"SFTP support requires paramiko to be available"}) host, port, username, password, rsa_key_path = self._yang_sftp_read_settings() model_list = self.parse_name_list(self.name) match = lambda n: (len(model_list) == 0) or (n in model_list) self.debug('connecting to {0}:{1} as {2}...'.format(host, port, username)) message = 'connection failed' try: with paramiko.Transport((host, port)) as transport: if password is None: pkey = paramiko.RSAKey.from_private_key_file(rsa_key_path) transport.connect(username=username, pkey=pkey) else: transport.connect(username=username, password=password) with paramiko.SFTPClient.from_transport(transport) as sftp: names = [name for name in sftp.listdir(self.remote_path) if name.endswith('.yang') and match(name)] self.progress_msg("Downloading {0} files using SFTP...\n".format(len(names))) for name in names: remote_path = '{0}/{1}'.format(self.remote_path, name) local_path = os.path.join(self.yang_directory, name) sftp.get(remote_path, local_path) message = 'transferred {0} files'.format(len(names)) except Exception as e: message = 'error occured {0}'.format(e) self.debug(message) self.debug(traceback.format_exc()) return {'yang-directory':self.yang_directory, 'message':message}
def abort(self, msg): raise ActionError({'error': msg})