jenkins源码分析 —— 执行主节点的远程请求(四)

本文解析jenkins从节点执行主节点的远程请求过程的源码

从上一篇文章“接受主节点的远程请求(三)”得知,执行UserRequst是通过创建Runnable并提交到Executor,而Executor负责执行构建

执行请求

具体执行请求的代码是UserRequest类的perform()方法

protected UserResponse<RSP,EXC> perform(Channel channel) throws EXC {
   try {
        ClassLoader cl = channel.importedClassLoaders.get(classLoaderProxy);

        RSP r = null;
        Channel oldc = Channel.setCurrent(channel);
       try {
            Object o;
           try {
                o = deserialize(channel,request,cl);
           } catch (ClassNotFoundException e) {
               throw new ClassNotFoundException("Failed to deserialize the Callable object. Perhaps you needed to implement DelegatingCallable?",e);
           } catch (RuntimeException e) {
               throw new Error("Failed to deserialize the Callable object.",e);
           }

            Callable<RSP,EXC> callable = (Callable<RSP,EXC>)o;
           if(!channel.isArbitraryCallableAllowed() && !(callable instanceof RPCRequest))
               throw new SecurityException("Execution of "+callable.toString()+" is prohibited because the channel is restricted");

            callable = channel.decorators.wrapUserRequest(callable);

            ClassLoader old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(cl);
           // execute the service
           try {
                r = callable.call();
           } finally {
                Thread.currentThread().setContextClassLoader(old);
           }
       } finally {
            Channel.setCurrent(oldc);
       }

       return new UserResponse<RSP,EXC>(serialize(r,channel),false);
       ...
}

创建ClassLoader

通过UserRequest类的perform()方法里面的ClassLoader cl = channel.importedClassLoaders.get(classLoaderProxy)创建ClassLoader 

反序列化Callable

通过UserRequest类的perform()方法里面Object o = deserialize(channel,request,cl)以及Callable<RSP,EXC> callable = (Callable<RSP,EXC>)o得到Callable对象,即“发送远程请求(二)”发送的RemoteLaunchCallable

deserialize()方法代码:

/*package*/ static Object deserialize(final Channel channel, byte[] data, ClassLoader defaultClassLoader) throws IOException, ClassNotFoundException {
    ByteArrayInputStream in = new ByteArrayInputStream(data);

    ObjectInputStream ois;
   if (channel.remoteCapability.supportsMultiClassLoaderRPC()) {
       // this code is coupled with the ObjectOutputStream subtype above
       ois = new MultiClassLoaderSerializer.Input(channel, in);
   } else {
        ois = new ObjectInputStreamEx(in, defaultClassLoader);
   }
   return ois.readObject();
}

MultiClassLoaderSerializer.Input继承ObjectInputStream,通过重写resolveClass,resolveProxyClass方法以及readClassLoader方法来获得ObjectInputStream对象

这么做的原因:反序列化时,如果在本地找不到这个对象的类的sourceCode,则序列化就会失败。但是可以通过覆盖ObjectOutputStream.annotateClass和ObjectInputStream.resolveClass来实现从主节点将类的sourceCode传到从节点,并运用ClassLoader来载入这个类。

class MultiClassLoaderSerializer {
 ...
 static final class Input extends ObjectInputStream {
       private final Channel channel;
       private final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();

        Input(Channel channel, InputStream in) throws IOException {
           super(in);
           this.channel = channel;
       }

       private ClassLoader readClassLoader() throws IOException, ClassNotFoundException {
       ...
       }
       @Override
       protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
       ...
       }
       @Override
       protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
       ...
       }

 }
 ...
}

执行callable.call

通过UserRequest类的perform()方法里面callable.call()调用jenkins-core项目的Launcher类的内部类RemoteLaunchCallable类的call方法

   private static class RemoteLaunchCallable extends MasterToSlaveCallable<RemoteProcess,IOException> {
       ...
       public RemoteProcess call() throws IOException {
            Launcher.ProcStarter ps = new LocalLauncher(listener).launch();
            ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err).quiet(quiet);
           if(workDir!=null)   ps.pwd(workDir);
           if (reverseStdin)   ps.writeStdin();
           if (reverseStdout)  ps.readStdout();
           if (reverseStderr)  ps.readStderr();

           final Proc p = ps.start();
       ...
   }

通过上面代码的Launcher.ProcStarter ps = new LocalLauncher(listener).launch()以及final Proc p = ps.start(),最终调用Launcher类的LocalLauncher内部类的launch方法

   public static class LocalLauncher extends Launcher {
   ...
       @Override
       public Proc launch(ProcStarter ps) throws IOException {
           if (!ps.quiet) {
                maskedPrintCommandLine(ps.commands, ps.masks, ps.pwd);
           }

            EnvVars jobEnv = inherit(ps.envs);

           // replace variables in command line
           String[] jobCmd = new String[ps.commands.size()];
           for ( int idx = 0 ; idx < jobCmd.length; idx++ )
             jobCmd[idx] = jobEnv.expand(ps.commands.get(idx));

           return new LocalProc(jobCmd, Util.mapToEnv(jobEnv),
                    ps.reverseStdin ?LocalProc.SELFPUMP_INPUT:ps.stdin,
                    ps.reverseStdout?LocalProc.SELFPUMP_OUTPUT:ps.stdout,
                    ps.reverseStderr?LocalProc.SELFPUMP_OUTPUT:ps.stderr,
                    toFile(ps.pwd));
       }
      ...
   }

而LocalProc构造方法

       public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out,OutputStream err,File workDir) throws IOException {
           this( calcName(cmd),
                  stderr(environment(new ProcessBuilder(cmd),env).directory(workDir), err==null || err== SELFPUMP_OUTPUT),
                  in, out, err );
       }

里面的java.lang.ProcessBuilder才是jenkins job里面Execute Shell的最根本的代码。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Jenkins是一个流行的自动化构建工具,可以通过Publish Over SSH插件方便地部署代码到远程服务器。通过设置该插件,我们可以连接到远程服务器,并将构建后的文件和脚本发送到该服务器上。在Windows环境下,在Jenkins中设置Publish Over SSH插件时需要先安装PuTTY。 在Jenkins中配置Publish Over SSH插件的过程如下: 1. 首先需要在Jenkins服务器和目标服务器之间建立SSH连接,确保Jenkins服务器可以通过SSH连接到目标服务器。 2. 在Jenkins中安装Publish Over SSH插件。 3. 在Jenkins管理员界面的Manage Jenkins - Configure System - Publish over SSH中点击新增远程服务器。 4. 在远程服务器配置页面中需要输入目标服务器的SSH连接信息,包括远程服务器的IP地址、SSH端口号、登录用户,以及登录远程服务器所需的RSA私钥或用户名和密码。 5. 测试SSH连接是否可用,若成功则可以保存配置。 6. 在任务的构建步骤中可以选择Publish Over SSH插件,并设置构建后要部署的文件和脚本。 7. 如果需要执行远程脚本,则可以在Publish Over SSH插件中设置一个“远程命令”步骤,执行远程服务器上的Shell脚本或Windows批处理脚本。 在Windows环境下,通过Publish Over SSH插件执行远程bat脚本的步骤如下: 1. 在Jenkins任务的构建步骤中选择Publish Over SSH插件。 2. 在Publish Over SSH插件中选择“Advanced”选项卡,并在“Exec command”输入框中输入要执行的bat脚本路径和名称。 3. 点击保存并运行构建即可。Jenkins将会连接到远程服务器上,执行指定的bat脚本。 总的来说,使用Jenkins的Publish Over SSH插件可以方便地在Windows环境下向远程服务器部署代码和脚本,并执行远程服务器上的bat脚本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lovelife110

你的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值