Review Board 1.7.22


BIGTOP-835. The shell exec method must have variants which have timeout and can run in background

Review Request #9166 - Created Jan. 30, 2013 and updated

Hari Shreedharan
BIGTOP-835
Reviewers
bigtop
rvs
bigtop
Added new methods which can execute a script for a specified timeout and can also execute in the background. 
Added unit tests for new functionality. Current unit tests pass
bigtop-test-framework/src/main/groovy/org/apache/bigtop/itest/shell/Shell.groovy
Revision ae3da68 New Change
[20] 59 lines
[+20]
60
   *
60
   *
61
   * After executing the script its return code can be accessed as getRet(),
61
   * After executing the script its return code can be accessed as getRet(),
62
   * stdout as getOut() and stderr as getErr(). The script itself can be accessed
62
   * stdout as getOut() and stderr as getErr(). The script itself can be accessed
63
   * as getScript()
63
   * as getScript()
64
   * WARNING: it isn't thread safe
64
   * WARNING: it isn't thread safe

    
   
65
   * @param timeout timeout in milliseconds to wait before killing the script.

    
   
66
   * If timeout < 0, then this method will wait until the script completes

    
   
67
   * and will not be killed.
65
   * @param args shell script split into multiple Strings
68
   * @param args shell script split into multiple Strings
66
   * @return Shell object for chaining
69
   * @return Shell object for chaining
67
   */
70
   */
68
  Shell exec(Object... args) {
71
  Shell execWithTimeout(int timeout, Object... args) {
69
    def proc = user ? "sudo -u $user PATH=${System.getenv('PATH')} $shell".execute() :
72
    def proc = user ? "sudo -u $user PATH=${System.getenv('PATH')} $shell".execute() :
70
                                    "$shell".execute()
73
                                    "$shell".execute()
71
    script = args.join("\n")
74
    script = args.join("\n")
72
    if (LOG.isTraceEnabled()) {
75
    if (LOG.isTraceEnabled()) {
73
        LOG.trace("${shell} << __EOT__\n${script}\n__EOT__");
76
        LOG.trace("${shell} << __EOT__\n${script}\n__EOT__");
74
    }
77
    }
75

    
   
78

   
76
    Thread.start {
79
    Thread.start {
77
      def writer = new PrintWriter(new BufferedOutputStream(proc.out))
80
      def writer = new PrintWriter(new BufferedOutputStream(proc.out))
78
      writer.println(script)
81
      writer.println(script)
79
      writer.close()
82
      writer.close()
80
    }
83
    }
81
    ByteArrayOutputStream baosErr = new ByteArrayOutputStream(4096);
84
    ByteArrayOutputStream outStream = new ByteArrayOutputStream(4096)
82
    proc.consumeProcessErrorStream(baosErr);
85
    ByteArrayOutputStream errStream = new ByteArrayOutputStream(4096)
83
    out = proc.in.readLines()
86
    Thread.start {

    
   
87
      proc.consumeProcessOutput(outStream, errStream)

    
   
88
    }

    
   
89
    if (timeout >= 0) {

    
   
90
      proc.waitForOrKill(timeout)

    
   
91
    } else {

    
   
92
      proc.waitFor()

    
   
93
    }
84

    
   
94

   
85
    // Possibly a bug in String.split as it generates a 1-element array on an
95
    // Possibly a bug in String.split as it generates a 1-element array on an
86
    // empty String
96
    // empty String
87
    if (baosErr.size() != 0) {
97
    if (outStream.size() != 0) {
88
      err = baosErr.toString().split('\n');
98
      out = outStream.toString().split('\n')

    
   
99
    } else {

    
   
100
      out = Collections.EMPTY_LIST

    
   
101
    }

    
   
102
    if (errStream.size() != 0) {

    
   
103
      err = errStream.toString().split('\n')
89
    }
104
    }
90
    else {
105
    else {
91
      err = new ArrayList<String>();
106
      err = Collections.EMPTY_LIST
92
    }
107
    }
93

    
   

   
94
    proc.waitFor()

   
95
    ret = proc.exitValue()
108
    ret = proc.exitValue()
96

    
   
109

   
97
    if (LOG.isTraceEnabled()) {
110
    if (LOG.isTraceEnabled()) {
98
        if (ret != 0) {
111
        if (ret != 0) {
99
           LOG.trace("return: $ret");
112
           LOG.trace("return: $ret");
100
        }
113
        }
101
        if (out.size() != 0) {
114
        if (out.size() != 0) {
102
           LOG.trace("\n<stdout>\n${out.join('\n')}\n</stdout>");
115
           LOG.trace("\n<stdout>\n${out.join('\n')}\n</stdout>");
103
        }
116
        }
104
        if (err.size() != 0) {
117
        if (err.size() != 0) {
105
           LOG.trace("\n<stderr>\n${err.join('\n')}\n</stderr>");
118
           LOG.trace("\n<stderr>\n${err.join('\n')}\n</stderr>");
106
        }
119
        }
107
    }
120
    }

    
   
121
    return this

    
   
122
  }

    
   
123

   

    
   
124
  /**

    
   
125
   * Execute shell script consisting of as many Strings as we have arguments,

    
   
126
   * possibly under an explicit username (requires sudoers privileges).

    
   
127
   * NOTE: individual strings are concatenated into a single script as though

    
   
128
   * they were delimited with new line character. All quoting rules are exactly

    
   
129
   * what one would expect in standalone shell script.

    
   
130
   *

    
   
131
   * After executing the script its return code can be accessed as getRet(),

    
   
132
   * stdout as getOut() and stderr as getErr(). The script itself can be accessed

    
   
133
   * as getScript()

    
   
134
   * WARNING: it isn't thread safe

    
   
135
   * @param timeout timeout in milliseconds to wait before killing the script

    
   
136
   * . If timeout < 0, then this method will wait until the script completes

    
   
137
   * and will not be killed.

    
   
138
   * @param args shell script split into multiple Strings

    
   
139
   * @return Shell object for chaining

    
   
140
   */

    
   
141
  Shell exec(Object... args) {

    
   
142
    return execWithTimeout(-1, args)

    
   
143
  }

    
   
144

   

    
   
145
  /**

    
   
146
   * Executes a shell script consisting of as many strings as we have args,

    
   
147
   * under an explicit user name. This method does the same job as

    
   
148
   * {@linkplain #exec(java.lang.Object[])}, but will return immediately,

    
   
149
   * with the process continuing execution in the background. If this method

    
   
150
   * is called, the output stream and error stream of this script  will be

    
   
151
   * available in the {@linkplain #out} and {@linkplain #err} lists.

    
   
152
   * WARNING: it isn't thread safe

    
   
153
   * <strong>CAUTION:</strong>

    
   
154
   * If this shell object is used to run other script while a script is

    
   
155
   * being executed in the background, then the output stream and error

    
   
156
   * stream of the script executed later will be what is available,

    
   
157
   * and the output and error streams of this script may be lost.

    
   
158
   * @param args

    
   
159
   * @return Shell object for chaining

    
   
160
   */

    
   
161
  Shell fork(Object... args) {

    
   
162
    forkWithTimeout(-1, args)

    
   
163
    return this

    
   
164
  }
108

    
   
165

   

    
   
166
  /**

    
   
167
   * Executes a shell script consisting of as many strings as we have args,

    
   
168
   * under an explicit user name. This method does the same job as

    
   
169
   * {@linkplain #execWithTimeout(int, java.lang.Object[])}, but will return immediately,

    
   
170
   * with the process continuing execution in the background for timeout

    
   
171
   * milliseconds (or until the script completes , whichever is earlier). If

    
   
172
   * this method

    
   
173
   * is called, the output stream and error stream of this script will be

    
   
174
   * available in the {@linkplain #out} and {@linkplain #err} lists.

    
   
175
   * WARNING: it isn't thread safe

    
   
176
   * <strong>CAUTION:</strong>

    
   
177
   * If this shell object is used to run other script while a script is

    
   
178
   * being executed in the background, then the output stream and error

    
   
179
   * stream of the script executed later will be what is available,

    
   
180
   * and the output and error streams of this script may be lost.

    
   
181
   * @param timeout The timoeut in milliseconds before the script is killed

    
   
182
   * @param args

    
   
183
   * @return Shell object for chaining

    
   
184
   */

    
   
185
  Shell forkWithTimeout(int timeout, Object... args) {

    
   
186
    Thread.start {

    
   
187
      execWithTimeout(timeout, args)

    
   
188
    }
109
    return this
189
    return this
110
  }
190
  }
111
}
191
}
bigtop-test-framework/src/test/groovy/org/apache/bigtop/itest/shell/ShellTest.groovy
Revision 1571e10 New Change
 
  1. bigtop-test-framework/src/main/groovy/org/apache/bigtop/itest/shell/Shell.groovy: Loading...
  2. bigtop-test-framework/src/test/groovy/org/apache/bigtop/itest/shell/ShellTest.groovy: Loading...