1 | /** |
2 | * |
3 | * Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com> |
4 | * |
5 | * ==================================================================== |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * you may not use this file except in compliance with the License. |
8 | * You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, software |
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * See the License for the specific language governing permissions and |
16 | * limitations under the License. |
17 | * ==================================================================== |
18 | */ |
19 | package org.jclouds.compute.callables; |
20 | |
21 | import static com.google.common.base.Preconditions.checkNotNull; |
22 | import static com.google.common.base.Preconditions.checkState; |
23 | |
24 | import java.util.Collections; |
25 | |
26 | import javax.annotation.Resource; |
27 | import javax.inject.Named; |
28 | |
29 | import org.jclouds.compute.domain.ExecResponse; |
30 | import org.jclouds.compute.domain.NodeMetadata; |
31 | import org.jclouds.compute.options.RunScriptOptions; |
32 | import org.jclouds.compute.reference.ComputeServiceConstants; |
33 | import org.jclouds.logging.Logger; |
34 | import org.jclouds.scriptbuilder.InitBuilder; |
35 | import org.jclouds.scriptbuilder.domain.OsFamily; |
36 | import org.jclouds.scriptbuilder.domain.Statement; |
37 | import org.jclouds.ssh.SshClient; |
38 | |
39 | import com.google.common.annotations.VisibleForTesting; |
40 | import com.google.common.base.Function; |
41 | import com.google.common.base.Objects; |
42 | import com.google.inject.assistedinject.Assisted; |
43 | import com.google.inject.assistedinject.AssistedInject; |
44 | |
45 | /** |
46 | * |
47 | * @author Adrian Cole |
48 | */ |
49 | public class RunScriptOnNodeAsInitScriptUsingSsh implements RunScriptOnNode { |
50 | @Resource |
51 | @Named(ComputeServiceConstants.COMPUTE_LOGGER) |
52 | protected Logger logger = Logger.NULL; |
53 | |
54 | protected final Function<NodeMetadata, SshClient> sshFactory; |
55 | protected final NodeMetadata node; |
56 | protected final Statement init; |
57 | protected final String name; |
58 | protected final boolean runAsRoot; |
59 | |
60 | protected SshClient ssh; |
61 | |
62 | @AssistedInject |
63 | public RunScriptOnNodeAsInitScriptUsingSsh(Function<NodeMetadata, SshClient> sshFactory, |
64 | @Assisted NodeMetadata node, @Assisted Statement script, @Assisted RunScriptOptions options) { |
65 | this.sshFactory = checkNotNull(sshFactory, "sshFactory"); |
66 | this.node = checkNotNull(node, "node"); |
67 | String name = options.getTaskName(); |
68 | if (name == null) { |
69 | if (checkNotNull(script, "script") instanceof InitBuilder) |
70 | name = InitBuilder.class.cast(script).getInstanceName(); |
71 | else |
72 | name = "jclouds-script-" + System.currentTimeMillis(); |
73 | } |
74 | this.name = checkNotNull(name, "name"); |
75 | this.init = checkNotNull(script, "script") instanceof InitBuilder ? InitBuilder.class.cast(script) |
76 | : createInitScript(name, script); |
77 | this.runAsRoot = options.shouldRunAsRoot(); |
78 | } |
79 | |
80 | public static InitBuilder createInitScript(String name, Statement script) { |
81 | String path = "/tmp/" + name; |
82 | return new InitBuilder(name, path, path, Collections.<String, String> emptyMap(), Collections.singleton(script)); |
83 | } |
84 | |
85 | @Override |
86 | public ExecResponse call() { |
87 | checkState(ssh != null, "please call init() before invoking call"); |
88 | try { |
89 | ssh.connect(); |
90 | return doCall(); |
91 | } finally { |
92 | if (ssh != null) |
93 | ssh.disconnect(); |
94 | } |
95 | } |
96 | |
97 | @Override |
98 | public RunScriptOnNode init() { |
99 | ssh = sshFactory.apply(node); |
100 | return this; |
101 | } |
102 | |
103 | /** |
104 | * ssh client is initialized through this call. |
105 | */ |
106 | protected ExecResponse doCall() { |
107 | ssh.put(name, init.render(OsFamily.UNIX)); |
108 | ssh.exec("chmod 755 " + name); |
109 | runAction("init"); |
110 | return runAction("start"); |
111 | } |
112 | |
113 | protected ExecResponse runAction(String action) { |
114 | ExecResponse returnVal; |
115 | String command = (runAsRoot) ? execScriptAsRoot(action) : execScriptAsDefaultUser(action); |
116 | returnVal = runCommand(command); |
117 | if (logger.isTraceEnabled()) |
118 | logger.trace("<< %s[%s]", action, returnVal); |
119 | else |
120 | logger.debug("<< %s(%d)", action, returnVal.getExitCode()); |
121 | return returnVal; |
122 | } |
123 | |
124 | protected ExecResponse runCommand(String command) { |
125 | ExecResponse returnVal; |
126 | logger.debug(">> running [%s] as %s@%s", command.replace(node.getAdminPassword() != null ? node |
127 | .getAdminPassword() : "XXXXX", "XXXXX"), ssh.getUsername(), ssh.getHostAddress()); |
128 | returnVal = ssh.exec(command); |
129 | return returnVal; |
130 | } |
131 | |
132 | @VisibleForTesting |
133 | public String execScriptAsRoot(String action) { |
134 | String command; |
135 | if (node.getCredentials().identity.equals("root")) { |
136 | command = "./" + name + " " + action; |
137 | } else if (node.getAdminPassword() != null) { |
138 | command = String.format("echo '%s'|sudo -S ./%s %s", node.getAdminPassword(), name, action); |
139 | } else { |
140 | command = "sudo ./" + name + " " + action; |
141 | } |
142 | return command; |
143 | } |
144 | |
145 | protected String execScriptAsDefaultUser(String action) { |
146 | return "./" + name + " " + action; |
147 | } |
148 | |
149 | public NodeMetadata getNode() { |
150 | return node; |
151 | } |
152 | |
153 | @Override |
154 | public String toString() { |
155 | return Objects.toStringHelper(this).add("node", node).add("name", name).add("runAsRoot", runAsRoot).toString(); |
156 | } |
157 | |
158 | } |