File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\os\solaris\ZFSInstaller.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.os.solaris;
25
26 import com.sun.akuma.Daemon;
27 import com.sun.akuma.JavaVMArguments;
28 import hudson.Launcher.LocalLauncher;
29 import hudson.Util;
30 import hudson.Extension;
31 import hudson.os.SU;
32 import hudson.model.AdministrativeMonitor;
33 import jenkins.model.Jenkins;
34 import hudson.model.TaskListener;
35 import hudson.remoting.Callable;
36 import hudson.util.ForkOutputStream;
37 import hudson.util.HudsonIsRestarting;
38 import hudson.util.StreamTaskListener;
39 import static hudson.util.jna.GNUCLibrary.*;
40
41 import org.apache.commons.io.output.ByteArrayOutputStream;
42 import org.jvnet.libpam.impl.CLibrary.passwd;
43 import org.jvnet.solaris.libzfs.ACLBuilder;
44 import org.jvnet.solaris.libzfs.LibZFS;
45 import org.jvnet.solaris.libzfs.ZFSException;
46 import org.jvnet.solaris.libzfs.ZFSFileSystem;
47 import org.jvnet.solaris.libzfs.ErrorCode;
48 import org.jvnet.solaris.mount.MountFlags;
49 import org.kohsuke.stapler.StaplerRequest;
50 import org.kohsuke.stapler.StaplerResponse;
51 import org.kohsuke.stapler.QueryParameter;
52 import org.kohsuke.stapler.HttpResponse;
53 import org.kohsuke.stapler.HttpResponses;
54 import org.kohsuke.stapler.HttpRedirect;
55 import org.kohsuke.stapler.interceptor.RequirePOST;
56
57 import javax.servlet.ServletException;
58 import java.io.File;
59 import java.io.IOException;
60 import java.io.PrintStream;
61 import java.io.Serializable;
62 import java.util.List;
63 import java.util.logging.Level;
64 import java.util.logging.Logger;
65
66 /**
67 * Encourages the user to migrate JENKINS_HOME on a ZFS file system.
68 *
69 * @author Kohsuke Kawaguchi
70 * @since 1.283
71 */
72 public class ZFSInstaller extends AdministrativeMonitor implements Serializable {
73 private static final long serialVersionUID = 1018007614648118323L;
74
75 /**
76 * True if $JENKINS_HOME is a ZFS file system by itself.
77 */
78 private final boolean active = shouldBeActive();
79
80 /**
81 * This will be the file system name that we'll create.
82 */
83 private String prospectiveZfsFileSystemName;
84
85 public boolean isActivated() {
86 return active;
87 }
88
89 public boolean isRoot() {
90 return LIBC.geteuid()==0;
91 }
92
93 public String getProspectiveZfsFileSystemName() {
94 return prospectiveZfsFileSystemName;
95 }
96
97 private boolean shouldBeActive() {
98 if(!System.getProperty("os.name").equals("SunOS") || disabled)
99 // on systems that don't have ZFS, we don't need this monitor
100 return false;
101
102 try {
103 LibZFS zfs = new LibZFS();
104 List<ZFSFileSystem> roots = zfs.roots();
105 if(roots.isEmpty())
106 return false; // no active ZFS pool
107
108 // if we don't run on a ZFS file system, activate
109 ZFSFileSystem hudsonZfs = zfs.getFileSystemByMountPoint(Jenkins.getInstance().getRootDir());
110 if(hudsonZfs!=null)
111 return false; // already on ZFS
112
113 // decide what file system we'll create
114 ZFSFileSystem pool = roots.get(0);
115 prospectiveZfsFileSystemName = computeHudsonFileSystemName(zfs,pool);
116
117 return true;
118 } catch (Exception e) {
119 LOGGER.log(Level.WARNING, "Failed to detect whether Hudson is on ZFS",e);
120 return false;
121 } catch (LinkageError e) {
122 LOGGER.info("No ZFS available. If you believe this is an error, increase the logging level to get the stack trace");
123 LOGGER.log(Level.FINE,"Stack trace of failed ZFS load",e);
124 return false;
125 }
126 }
127
128 /**
129 * Called from the management screen.
130 */
131 @RequirePOST
132 public HttpResponse doAct(StaplerRequest req) throws ServletException, IOException {
133 Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
134
135 if(req.hasParameter("n")) {
136 // we'll shut up
137 disable(true);
138 return HttpResponses.redirectViaContextPath("/manage");
139 }
140
141 return new HttpRedirect("confirm");
142 }
143
144 /**
145 * Creates a ZFS file system to migrate the data to.
146 *
147 * <p>
148 * This has to be done while we still have an interactive access with the user, since it involves the password.
149 *
150 * <p>
151 * An exception will be thrown if the operation fails. A normal completion means a success.
152 *
153 * @return
154 * The ZFS dataset name to migrate the data to.
155 */
156 private String createZfsFileSystem(final TaskListener listener, String rootUsername, String rootPassword) throws IOException, InterruptedException, ZFSException {
157 // capture the UID that Hudson runs under
158 // so that we can allow this user to do everything on this new partition
159 final int uid = LIBC.geteuid();
160 final int gid = LIBC.getegid();
161 passwd pwd = LIBC.getpwuid(uid);
162 if(pwd==null)
163 throw new IOException("Failed to obtain the current user information for "+uid);
164 final String userName = pwd.pw_name;
165
166 final File home = Jenkins.getInstance().getRootDir();
167
168 // this is the actual creation of the file system.
169 // return true indicating a success
170 return SU.execute(listener, rootUsername, rootPassword, new Callable<String,IOException>() {
171 private static final long serialVersionUID = 7731167233498214301L;
172
173 public String call() throws IOException {
174 PrintStream out = listener.getLogger();
175
176 LibZFS zfs = new LibZFS();
177 ZFSFileSystem existing = zfs.getFileSystemByMountPoint(home);
178 if(existing!=null) {
179 // no need for migration
180 out.println(home+" is already on ZFS. Doing nothing");
181 return existing.getName();
182 }
183
184 String name = computeHudsonFileSystemName(zfs, zfs.roots().get(0));
185 out.println("Creating "+name);
186 ZFSFileSystem hudson = zfs.create(name, ZFSFileSystem.class);
187
188 // mount temporarily to set the owner right
189 File dir = Util.createTempDir();
190 hudson.setMountPoint(dir);
191 hudson.mount();
192 if(LIBC.chown(dir.getPath(),uid,gid)!=0)
193 throw new IOException("Failed to chown "+dir);
194 hudson.unmount();
195
196 try {
197 hudson.setProperty("hudson:managed-by","hudson"); // mark this file system as "managed by Hudson"
198
199 ACLBuilder acl = new ACLBuilder();
200 acl.user(userName).withEverything();
201 hudson.allow(acl);
202 } catch (ZFSException e) {
203 // revert the file system creation
204 try {
205 hudson.destory();
206 } catch (Exception _) {
207 // but ignore the error and let the original error thrown
208 }
209 throw e;
210 }
211 return hudson.getName();
212 }
213 });
214 }
215
216 /**
217 * Called from the confirmation screen to actually initiate the migration.
218 */
219 @RequirePOST
220 public void doStart(StaplerRequest req, StaplerResponse rsp, @QueryParameter String username, @QueryParameter String password) throws ServletException, IOException {
221 Jenkins hudson = Jenkins.getInstance();
222 hudson.checkPermission(Jenkins.ADMINISTER);
223
224 final String datasetName;
225 ByteArrayOutputStream log = new ByteArrayOutputStream();
226 StreamTaskListener listener = new StreamTaskListener(log);
227 try {
228 datasetName = createZfsFileSystem(listener,username,password);
229 } catch (Exception e) {
230 e.printStackTrace(listener.error(e.getMessage()));
231
232 if (e instanceof ZFSException) {
233 ZFSException ze = (ZFSException) e;
234 if(ze.getCode()==ErrorCode.EZFS_PERM) {
235 // permission problem. ask the user to give us the root password
236 req.setAttribute("message",log.toString());
237 rsp.forward(this,"askRootPassword",req);
238 return;
239 }
240 }
241
242 // for other kinds of problems, report and bail out
243 req.setAttribute("pre",true);
244 sendError(log.toString(),req,rsp);
245 return;
246 }
247
248 // file system creation successful, so restart
249
250 hudson.servletContext.setAttribute("app",new HudsonIsRestarting());
251 // redirect the user to the manage page
252 rsp.sendRedirect2(req.getContextPath()+"/manage");
253
254 // asynchronously restart, so that we can give a bit of time to the browser to load "restarting..." screen.
255 new Thread("restart thread") {
256 @Override
257 public void run() {
258 try {
259 Thread.sleep(5000);
260
261 // close all descriptors on exec except stdin,out,err
262 int sz = LIBC.getdtablesize();
263 for(int i=3; i<sz; i++) {
264 int flags = LIBC.fcntl(i, F_GETFD);
265 if(flags<0) continue;
266 LIBC.fcntl(i, F_SETFD,flags| FD_CLOEXEC);
267 }
268
269 // re-exec with the system property to indicate where to migrate the data to.
270 // the 2nd phase is implemented in the migrate method.
271 JavaVMArguments args = JavaVMArguments.current();
272 args.setSystemProperty(ZFSInstaller.class.getName()+".migrate",datasetName);
273 Daemon.selfExec(args);
274 } catch (InterruptedException e) {
275 LOGGER.log(Level.SEVERE, "Restart failed",e);
276 } catch (IOException e) {
277 LOGGER.log(Level.SEVERE, "Restart failed",e);
278 }
279 }
280 }.start();
281 }
282
283 @Extension
284 public static AdministrativeMonitor init() {
285 String migrationTarget = System.getProperty(ZFSInstaller.class.getName() + ".migrate");
286 if(migrationTarget!=null) {
287 ByteArrayOutputStream out = new ByteArrayOutputStream();
288 StreamTaskListener listener = new StreamTaskListener(new ForkOutputStream(System.out, out));
289 try {
290 if(migrate(listener,migrationTarget)) {
291 // completed successfully
292 return new MigrationCompleteNotice();
293 }
294 } catch (Exception e) {
295 // if we let any exception from here, it will prevent Hudson from starting.
296 e.printStackTrace(listener.error("Migration failed"));
297 }
298 // migration failed
299 return new MigrationFailedNotice(out);
300 }
301
302 // install the monitor if applicable
303 ZFSInstaller zi = new ZFSInstaller();
304 if(zi.isActivated())
305 return zi;
306
307 return null;
308 }
309
310 /**
311 * Migrates $JENKINS_HOME to a new ZFS file system.
312 *
313 * TODO: do this in a separate JVM to elevate the privilege.
314 *
315 * @param listener
316 * Log of migration goes here.
317 * @param target
318 * Dataset to move the data to.
319 * @return
320 * false if a migration failed.
321 */
322 private static boolean migrate(TaskListener listener, String target) throws IOException, InterruptedException {
323 PrintStream out = listener.getLogger();
324
325 File home = Jenkins.getInstance().getRootDir();
326 // do the migration
327 LibZFS zfs = new LibZFS();
328 ZFSFileSystem existing = zfs.getFileSystemByMountPoint(home);
329 if(existing!=null) {
330 out.println(home+" is already on ZFS. Doing nothing");
331 return true;
332 }
333
334 File tmpDir = Util.createTempDir();
335
336 // mount a new file system to a temporary location
337 out.println("Opening "+target);
338 ZFSFileSystem hudson = zfs.open(target, ZFSFileSystem.class);
339 hudson.setMountPoint(tmpDir);
340 hudson.setProperty("hudson:managed-by","hudson"); // mark this file system as "managed by Hudson"
341 hudson.mount();
342
343 // copy all the files
344 out.println("Copying all existing data files");
345 if(system(home,listener, "/usr/bin/cp","-pR",".", tmpDir.getAbsolutePath())!=0) {
346 out.println("Failed to copy "+home+" to "+tmpDir);
347 return false;
348 }
349
350 // unmount
351 out.println("Unmounting "+target);
352 hudson.unmount(MountFlags.MS_FORCE);
353
354 // move the original directory to the side
355 File backup = new File(home.getPath()+".backup");
356 out.println("Moving "+home+" to "+backup);
357 if(backup.exists())
358 Util.deleteRecursive(backup);
359 if(!home.renameTo(backup)) {
360 out.println("Failed to move your current data "+home+" out of the way");
361 }
362
363 // update the mount point
364 out.println("Creating a new mount point at "+home);
365 if(!home.mkdir())
366 throw new IOException("Failed to create mount point "+home);
367
368 out.println("Mounting "+target);
369 hudson.setMountPoint(home);
370 hudson.mount();
371
372 out.println("Sharing "+target);
373 try {
374 hudson.setProperty("sharesmb","on");
375 hudson.setProperty("sharenfs","on");
376 hudson.share();
377 } catch (ZFSException e) {
378 listener.error("Failed to share the file systems: "+e.getCode());
379 }
380
381 // delete back up
382 out.println("Deleting "+backup);
383 if(system(new File("/"),listener,"/usr/bin/rm","-rf",backup.getAbsolutePath())!=0) {
384 out.println("Failed to delete "+backup.getAbsolutePath());
385 return false;
386 }
387
388 out.println("Migration completed");
389 return true;
390 }
391
392 private static int system(File pwd, TaskListener listener, String... args) throws IOException, InterruptedException {
393 return new LocalLauncher(listener).launch().cmds(args).stdout(System.out).pwd(pwd).join();
394 }
395
396 private static String computeHudsonFileSystemName(LibZFS zfs, ZFSFileSystem top) {
397 if(!zfs.exists(top.getName()+"/hudson"))
398 return top.getName()+"/hudson";
399 for( int i=2; ; i++ ) {
400 String name = top.getName() + "/hudson" + i;
401 if(!zfs.exists(name))
402 return name;
403 }
404 }
405
406 /**
407 * Used to indicate that the migration was completed successfully.
408 */
409 public static final class MigrationCompleteNotice extends AdministrativeMonitor {
410 public boolean isActivated() {
411 return true;
412 }
413 }
414
415 /**
416 * Used to indicate a failure in the migration.
417 */
418 public static final class MigrationFailedNotice extends AdministrativeMonitor {
419 ByteArrayOutputStream record;
420
421 MigrationFailedNotice(ByteArrayOutputStream record) {
422 this.record = record;
423 }
424
425 public boolean isActivated() {
426 return true;
427 }
428
429 public String getLog() {
430 return record.toString();
431 }
432 }
433
434 private static final Logger LOGGER = Logger.getLogger(ZFSInstaller.class.getName());
435
436 /**
437 * Escape hatch in case JNI calls fatally crash, like in HUDSON-3733.
438 */
439 public static boolean disabled = Boolean.getBoolean(ZFSInstaller.class.getName()+".disabled");
440 }
441