issue 384: refactor sendScancode

This commit is contained in:
Andrea Turli 2012-01-27 12:24:17 +00:00
parent c04da585af
commit 01c3319258
4 changed files with 316 additions and 225 deletions

View File

@ -18,31 +18,35 @@
*/
package org.jclouds.virtualbox.functions;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.inject.Inject;
import org.jclouds.compute.callables.RunScriptOnNode.Factory;
import org.jclouds.compute.domain.NodeMetadata;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.transform;
import java.net.URI;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.ssh.SshClient;
import org.jclouds.virtualbox.domain.ExecutionType;
import org.jclouds.virtualbox.domain.IMachineSpec;
import org.jclouds.virtualbox.domain.IsoSpec;
import org.jclouds.virtualbox.domain.VmSpec;
import org.jclouds.virtualbox.settings.KeyboardScancodes;
import org.jclouds.virtualbox.util.MachineUtils;
import org.virtualbox_4_1.*;
import org.virtualbox_4_1.IMachine;
import org.virtualbox_4_1.IProgress;
import org.virtualbox_4_1.ISession;
import org.virtualbox_4_1.LockType;
import org.virtualbox_4_1.VirtualBoxManager;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import java.net.URI;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.inject.Inject;
@Singleton
public class CreateAndInstallVm implements Function<IMachineSpec, IMachine> {
@ -56,28 +60,19 @@ public class CreateAndInstallVm implements Function<IMachineSpec, IMachine> {
private final Predicate<SshClient> sshResponds;
private final ExecutionType executionType;
private final Factory scriptRunner;
private final Supplier<NodeMetadata> host;
private final Function<IMachine, SshClient> sshClientForIMachine;
private final MachineUtils machineUtils;
@Inject
public CreateAndInstallVm(
Supplier<VirtualBoxManager> manager,
CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists,
Predicate<SshClient> sshResponds,
Function<IMachine, SshClient> sshClientForIMachine,
Supplier<NodeMetadata> host, Factory scriptRunner,
ExecutionType executionType, MachineUtils machineUtils) {
public CreateAndInstallVm(Supplier<VirtualBoxManager> manager,
CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists,
Predicate<SshClient> sshResponds, Function<IMachine, SshClient> sshClientForIMachine,
ExecutionType executionType, MachineUtils machineUtils) {
this.manager = manager;
this.createAndRegisterMachineFromIsoIfNotAlreadyExists = CreateAndRegisterMachineFromIsoIfNotAlreadyExists;
this.sshResponds = sshResponds;
this.sshClientForIMachine = sshClientForIMachine;
this.scriptRunner = scriptRunner;
this.host = host;
this.executionType = executionType;
this.machineUtils = machineUtils;
}
@ -90,99 +85,55 @@ public class CreateAndInstallVm implements Function<IMachineSpec, IMachine> {
String vmName = vmSpec.getVmName();
final IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists
.apply(machineSpec);
final IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists.apply(machineSpec);
// Launch machine and wait for it to come online
ensureMachineIsLaunched(vmName);
URI uri = isoSpec.getPreConfigurationUri().get();
String installationKeySequence = isoSpec.getInstallationKeySequence()
.replace("PRECONFIGURATION_URL", uri.toASCIIString());
sendKeyboardSequence(installationKeySequence, vmName);
String installationKeySequence = isoSpec.getInstallationKeySequence().replace("PRECONFIGURATION_URL",
uri.toASCIIString());
configureOsInstallationWithKeyboardSequence(vmName, installationKeySequence);
SshClient client = sshClientForIMachine.apply(vm);
logger.debug(">> awaiting installation to finish node(%s)", vmName);
checkState(sshResponds.apply(client),
"timed out waiting for guest %s to be accessible via ssh", vmName);
checkState(sshResponds.apply(client), "timed out waiting for guest %s to be accessible via ssh", vmName);
logger.debug("<< installation of image complete. Powering down node(%s)",
vmName);
logger.debug("<< installation of image complete. Powering down node(%s)", vmName);
ensureMachineHasPowerDown(vmName);
return vm;
}
private void configureOsInstallationWithKeyboardSequence(String vmName, String installationKeySequence) {
Iterable<List<Integer>> scancodelist =
transform(Splitter.on(" ").split(installationKeySequence), new StringToKeyCode());
for (List<Integer> scancodes : scancodelist) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new SendScancode(scancodes));
// this is needed to avoid to miss any scancode
try {
Thread.sleep(300);
} catch (InterruptedException e) {
logger.error("Problem in sleeping the current thread.", e);
}
}
}
private void ensureMachineHasPowerDown(String vmName) {
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared,
new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole()
.powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new Function<ISession, Void>() {
@Override
public Void apply(ISession session) {
IProgress powerDownProgress = session.getConsole().powerDown();
powerDownProgress.waitForCompletion(-1);
return null;
}
});
}
private void ensureMachineIsLaunched(String vmName) {
machineUtils.applyForMachine(vmName,
new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType,
""));
machineUtils.applyForMachine(vmName, new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, ""));
}
private void sendKeyboardSequence(String keyboardSequence, String vmName) {
String[] splitSequence = keyboardSequence.split(" ");
StringBuilder sb = new StringBuilder();
for (String line : splitSequence) {
String converted = stringToKeycode(line);
for (String word : converted.split(" ")) {
sb.append("VBoxManage controlvm ").append(vmName)
.append(" keyboardputscancode ").append(word).append("; ");
runScriptIfWordEndsWith(sb, word, "<Enter>");
runScriptIfWordEndsWith(sb, word, "<Return>");
}
}
}
private void runScriptIfWordEndsWith(StringBuilder sb, String word,
String key) {
if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get(key))) {
scriptRunner
.create(host.get(), Statements.exec(sb.toString()),
runAsRoot(false).wrapInInitScript(false)).init().call();
sb.delete(0, sb.length() - 1);
}
}
private String stringToKeycode(String s) {
StringBuilder keycodes = new StringBuilder();
if (s.startsWith("<")) {
String[] specials = s.split("<");
for (int i = 1; i < specials.length; i++) {
keycodes.append(
KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<"
+ specials[i])).append(" ");
}
return keycodes.toString();
}
int i = 0;
while (i < s.length()) {
String digit = s.substring(i, i + 1);
String hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP.get(digit);
keycodes.append(hex).append(" ");
if (i != 0 && i % 14 == 0)
keycodes.append(" ");
i++;
}
keycodes.append(
KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<Spacebar>"))
.append(" ");
return keycodes.toString();
}
}
}

View File

@ -0,0 +1,42 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.virtualbox.functions;
import java.util.List;
import org.virtualbox_4_1.ISession;
import com.google.common.base.Function;
class SendScancode implements Function<ISession, Void> {
private final List<Integer> scancodes;
public SendScancode(List<Integer> scancodes) {
this.scancodes = scancodes;
}
@Override
public Void apply(ISession iSession) {
for (Integer scancode : scancodes) {
iSession.getConsole().getKeyboard().putScancode(scancode);
}
return null;
}
}

View File

@ -0,0 +1,69 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.virtualbox.functions;
import java.util.ArrayList;
import java.util.List;
import org.jclouds.virtualbox.settings.KeyboardScancodes;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
public class StringToKeyCode implements Function<String, List<Integer>> {
@Override
public List<Integer> apply(String subsequence) {
return stringToKeycode(subsequence);
}
private List<Integer> stringToKeycode(String s) {
if (containsSpecialCharacter(s)) {
return transforSpecialCharIntoKeycodes(s);
} else {
return transformStandardCharacterIntoKeycodes(s);
}
}
private List<Integer> transformStandardCharacterIntoKeycodes(String s) {
List<Integer> values = new ArrayList<Integer>();
for (String digit : Splitter.fixedLength(1).split(s)) {
List<Integer> hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP_LIST.get(digit);
if (hex != null)
values.addAll(hex);
}
values.addAll(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP_LIST.get("<Spacebar>"));
return values;
}
private List<Integer> transforSpecialCharIntoKeycodes(String s) {
List<Integer> values = new ArrayList<Integer>();
for (String special : s.split("<")) {
List<Integer> value = KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP_LIST.get("<" + special);
if (value != null)
values.addAll(value);
}
return values;
}
private boolean containsSpecialCharacter(String s) {
return s.startsWith("<");
}
}

View File

@ -19,145 +19,174 @@
package org.jclouds.virtualbox.settings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KeyboardScancodes {
// http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
public static final Map<String, List<Integer>> SPECIAL_KEYBOARD_BUTTON_MAP_LIST = createSpecialCodeMap();
public static final Map<String, List<Integer>> NORMAL_KEYBOARD_BUTTON_MAP_LIST = createNormalCodeMap();
public static final Map<String, String> NORMAL_KEYBOARD_BUTTON_MAP = createMap();
public static final Map<String, String> SPECIAL_KEYBOARD_BUTTON_MAP = createSpecialMap();
public static CharToIntegersMapBuilder builder() {
return new CharToIntegersMapBuilder();
}
private static Map<String, List<Integer>> createNormalCodeMap() {
Map<String, List<Integer>> alphaToHex = KeyboardScancodes.builder()
.put("1", 0x02, 0x82)
.put("2", 0x03, 0x83)
.put("3", 0x04, 0x84)
.put("4", 0x05, 0x85)
.put("5", 0x06, 0x86)
.put("6", 0x07, 0x87)
.put("7", 0x08, 0x88)
.put("8", 0x09, 0x89)
.put("9", 0x0a, 0x8a)
.put("0", 0x0b, 0x8b)
private static Map<String, String> createMap() {
Map<String, String> alphaToHex = new HashMap<String, String>();
alphaToHex.put("1", "02 82");
alphaToHex.put("2", "03 83");
alphaToHex.put("3", "04 84");
alphaToHex.put("4", "05 85");
alphaToHex.put("5", "06 86");
alphaToHex.put("6", "07 87");
alphaToHex.put("7", "08 88");
alphaToHex.put("8", "09 89");
alphaToHex.put("9", "0a 8a");
alphaToHex.put("0", "0b 8b");
alphaToHex.put("-", "0c 8c");
alphaToHex.put("=", "0d 8d");
alphaToHex.put("Tab", "0f 8f");
alphaToHex.put("q", "10 90");
alphaToHex.put("w", "11 91");
alphaToHex.put("e", "12 92");
alphaToHex.put("r", "13 93");
alphaToHex.put("t", "14 94");
alphaToHex.put("y", "15 95");
alphaToHex.put("u", "16 96");
alphaToHex.put("i", "17 97");
alphaToHex.put("o", "18 98");
alphaToHex.put("p", "19 99");
.put("-", 0x0c, 0x8c)
.put("=", 0x0d, 0x8d)
.put("Tab", 0x0f, 0x8f)
.put("q", 0x10, 0x90)
.put("w", 0x11, 0x91)
.put("e", 0x12, 0x92)
.put("r", 0x13, 0x93)
.put("t", 0x14, 0x94)
.put("y", 0x15, 0x95)
.put("u", 0x16, 0x96)
.put("i", 0x17, 0x97)
.put("o", 0x18, 0x98)
.put("p", 0x19, 0x99)
alphaToHex.put("Q", "2a 10 aa");
alphaToHex.put("W", "2a 11 aa");
alphaToHex.put("E", "2a 12 aa");
alphaToHex.put("R", "2a 13 aa");
alphaToHex.put("T", "2a 14 aa");
alphaToHex.put("Y", "2a 15 aa");
alphaToHex.put("U", "2a 16 aa");
alphaToHex.put("I", "2a 17 aa");
alphaToHex.put("O", "2a 18 aa");
alphaToHex.put("P", "2a 19 aa");
.put("Q", 0x2a, 0x10, 0xaa)
.put("W", 0x2a, 0x11, 0xaa)
.put("E", 0x2a, 0x12, 0xaa)
.put("R", 0x2a, 0x13, 0xaa)
.put("T", 0x2a, 0x14, 0xaa)
.put("Y", 0x2a, 0x15, 0xaa)
.put("U", 0x2a, 0x16, 0xaa)
.put("I", 0x2a, 0x17, 0xaa)
.put("O", 0x2a, 0x18, 0xaa)
.put("P", 0x2a, 0x19, 0xaa)
alphaToHex.put("a", "1e 9e");
alphaToHex.put("s", "1f 9f");
alphaToHex.put("d", "20 a0");
alphaToHex.put("f", "21 a1");
alphaToHex.put("g", "22 a2");
alphaToHex.put("h", "23 a3");
alphaToHex.put("j", "24 a4");
alphaToHex.put("k", "25 a5");
alphaToHex.put("l", "26 a6");
.put("a", 0x1e, 0x9e)
.put("s", 0x1f, 0x9f)
.put("d", 0x20, 0xa0)
.put("f", 0x21, 0xa1)
.put("g", 0x22, 0xa2)
.put("h", 0x23, 0xa3)
.put("j", 0x24, 0xa4)
.put("k", 0x25, 0xa5)
.put("l", 0x26, 0xa6)
alphaToHex.put("A", "2a 1e aa 9e");
alphaToHex.put("S", "2a 1f aa 9f");
alphaToHex.put("D", "2a 20 aa a0");
alphaToHex.put("F", "2a 21 aa a1");
alphaToHex.put("G", "2a 22 aa a2");
alphaToHex.put("H", "2a 23 aa a3");
alphaToHex.put("J", "2a 24 aa a4");
alphaToHex.put("K", "2a 25 aa a5");
alphaToHex.put("L", "2a 26 aa a6");
.put("A", 0x2a, 0x1e, 0xaa, 0x9e)
.put("S", 0x2a, 0x1f, 0xaa, 0x9f)
.put("D", 0x2a, 0x20, 0xaa, 0xa0)
.put("F", 0x2a, 0x21, 0xaa, 0xa1)
.put("G", 0x2a, 0x22, 0xaa, 0xa2)
.put("H", 0x2a, 0x23, 0xaa, 0xa3)
.put("J", 0x2a, 0x24, 0xaa, 0xa4)
.put("K", 0x2a, 0x25, 0xaa, 0xa5)
.put("L", 0x2a, 0x26, 0xaa, 0xa6)
alphaToHex.put(");", "27 a7");
alphaToHex.put("\"", "2a 28 aa a8");
alphaToHex.put("\"", "28 a8");
alphaToHex.put("\\", "2b ab");
alphaToHex.put("|", "2a 2b aa 8b");
alphaToHex.put("[", "1a 9a");
alphaToHex.put("", "1b 9b");
alphaToHex.put("<", "2a 33 aa b3");
alphaToHex.put(">", "2a 34 aa b4");
alphaToHex.put("$", "2a 05 aa 85");
alphaToHex.put("+", "2a 0d aa 8d");
.put(") ", 0x27, 0xa7)
.put("\"", 0x2a, 0x28, 0xaa, 0xa8)
.put("\"", 0x28, 0xa8)
.put("\\", 0x2b, 0xab)
.put("|", 0x2a, 0x2b, 0xaa, 0x8b)
.put("[", 0x1a, 0x9a)
.put("", 0x1b, 0x9b)
.put("<", 0x2a, 0x33, 0xaa, 0xb3)
.put(">", 0x2a, 0x34, 0xaa, 0xb4)
.put("$", 0x2a, 0x05, 0xaa, 0x85)
.put("+", 0x2a, 0x0d, 0xaa, 0x8d)
.put("z", 0x2c, 0xac)
.put("x", 0x2d, 0xad)
.put("c", 0x2e, 0xae)
.put("v", 0x2f, 0xaf)
.put("b", 0x30, 0xb0)
.put("n", 0x31, 0xb1)
.put("m", 0x32, 0xb2)
.put("Z", 0x2a, 0x2c, 0xaa, 0xac)
.put("X", 0x2a, 0x2d, 0xaa, 0xad)
.put("C", 0x2a, 0x2e, 0xaa, 0xae)
.put("V", 0x2a, 0x2f, 0xaa, 0xaf)
.put("B", 0x2a, 0x30, 0xaa, 0xb0)
.put("N", 0x2a, 0x31, 0xaa, 0xb1)
.put("M", 0x2a, 0x32, 0xaa, 0xb2)
alphaToHex.put("z", "2c ac");
alphaToHex.put("x", "2d ad");
alphaToHex.put("c", "2e ae");
alphaToHex.put("v", "2f af");
alphaToHex.put("b", "30 b0");
alphaToHex.put("n", "31 b1");
alphaToHex.put("m", "32 b2");
alphaToHex.put("Z", "2a 2c aa ac");
alphaToHex.put("X", "2a 2d aa ad");
alphaToHex.put("C", "2a 2e aa ae");
alphaToHex.put("V", "2a 2f aa af");
alphaToHex.put("B", "2a 30 aa b0");
alphaToHex.put("N", "2a 31 aa b1");
alphaToHex.put("M", "2a 32 aa b2");
.put(",", 0x33, 0xb3)
.put(".", 0x34, 0xb4)
.put("/", 0x35, 0xb5)
.put(":", 0x2a, 0x27, 0xaa, 0xa7)
.put("%", 0x2a, 0x06, 0xaa, 0x86)
.put("_", 0x2a, 0x0c, 0xaa, 0x8c)
.put("&", 0x2a, 0x08, 0xaa, 0x88)
.put("(", 0x2a, 0x0a, 0xaa, 0x8a)
.put(")", 0x2a, 0x0b, 0xaa, 0x8b)
.build();
alphaToHex.put(",", "33 b3");
alphaToHex.put(".", "34 b4");
alphaToHex.put("/", "35 b5");
alphaToHex.put(":", "2a 27 aa a7");
alphaToHex.put("%", "2a 06 aa 86");
alphaToHex.put("_", "2a 0c aa 8c");
alphaToHex.put("&", "2a 08 aa 88");
alphaToHex.put("(", "2a 0a aa 8a");
alphaToHex.put(")", "2a 0b aa 8b");
return Collections.unmodifiableMap(alphaToHex);
}
private static Map<String, String> createSpecialMap() {
Map<String, String> special = new HashMap<String, String>();
special.put("<Enter>", "1c 9c");
special.put("<Backspace>", "0e 8e");
special.put("<Spacebar>", "39 b9");
special.put("<Return>", "1c 9c");
special.put("<Esc>", "01 81");
special.put("<Tab>", "0f 8f");
special.put("<KillX>", "1d 38 0e");
special.put("<Wait>", "wait");
private static Map<String, List<Integer>> createSpecialCodeMap() {
Map<String, List<Integer>> special = KeyboardScancodes
.builder()
.put("<Enter>", 0x1c, 0x9c)
.put("<Backspace>", 0x0e, 0x8e)
.put("<Spacebar>", 0x39, 0xb9)
.put("<Return>", 0x1c, 0x9c)
.put("<Esc>", 0x01, 0x81)
.put("<Tab>", 0x0f, 0x8f)
.put("<KillX>", 0x1d, 0x38, 0x0e)
special.put("<Up>", "48 c8");
special.put("<Down>", "50 d0");
special.put("<PageUp>", "49 c9");
special.put("<PageDown>", "51 d1");
special.put("<End>", "4f cf");
special.put("<Insert>", "52 d2");
special.put("<Delete>", "53 d3");
special.put("<Left>", "4b cb");
special.put("<Right>", "4d cd");
special.put("<Home>", "47 c7");
.put("<Up>", 0x48, 0xc8)
.put("<Down>", 0x50, 0xd0)
.put("<PageUp>", 0x49, 0xc9)
.put("<PageDown>", 0x51, 0xd1)
.put("<End>", 0x4f, 0xcf)
.put("<Insert>", 0x52, 0xd2)
.put("<Delete>", 0x53, 0xd3)
.put("<Left>", 0x4b, 0xcb)
.put("<Right>", 0x4d, 0xcd)
.put("<Home>", 0x47, 0xc7)
special.put("<F1>", "3b");
special.put("<F2>", "3c");
special.put("<F3>", "3d");
special.put("<F4>", "3e");
special.put("<F5>", "3f");
special.put("<F6>", "40");
special.put("<F7>", "41");
special.put("<F8>", "42");
special.put("<F9>", "43");
special.put("<F10>", "44");
.put("<F1>", 0x3b)
.put("<F2>", 0x3c)
.put("<F3>", 0x3d)
.put("<F4>", 0x3e)
.put("<F5>", 0x3f)
.put("<F6>", 0x40)
.put("<F7>", 0x41)
.put("<F8>", 0x42)
.put("<F9>", 0x43)
.put("<F10>", 0x44)
.build();
return Collections.unmodifiableMap(special);
}
}
public static class CharToIntegersMapBuilder {
private Map<String, List<Integer>> mappings = new HashMap<String, List<Integer>>();
public CharToIntegersMapBuilder put(String str, int... mapping) {
List<Integer> arrayList = new ArrayList<Integer>();
for (int i : mapping) {
arrayList.add(i);
}
mappings.put(str, arrayList);
return this;
}
public Map<String, List<Integer>> build() {
return mappings;
}
}
}