mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2025-02-24 13:30:02 +08:00
Implemented API for guest communication
This allows to send the shutdown command from the host to the guest
This commit is contained in:
parent
5761ad9b44
commit
1a10105f93
@ -2,7 +2,9 @@ FROM golang:1.20 AS builder
|
|||||||
|
|
||||||
COPY serial/ /src/serial/
|
COPY serial/ /src/serial/
|
||||||
WORKDIR /src/serial
|
WORKDIR /src/serial
|
||||||
|
|
||||||
RUN go get -d -v golang.org/x/net/html
|
RUN go get -d -v golang.org/x/net/html
|
||||||
|
RUN go get -d -v github.com/gorilla/mux
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main .
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main .
|
||||||
|
|
||||||
FROM debian:bookworm-20230320-slim
|
FROM debian:bookworm-20230320-slim
|
||||||
|
23
power.sh
23
power.sh
@ -4,7 +4,7 @@ set -eu
|
|||||||
# Configure QEMU for graceful shutdown
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
QEMU_MONPORT=7100
|
QEMU_MONPORT=7100
|
||||||
QEMU_POWERDOWN_TIMEOUT=30
|
QEMU_POWERDOWN_TIMEOUT=50
|
||||||
_QEMU_PID=/run/qemu.pid
|
_QEMU_PID=/run/qemu.pid
|
||||||
_QEMU_SHUTDOWN_COUNTER=/run/qemu.counter
|
_QEMU_SHUTDOWN_COUNTER=/run/qemu.counter
|
||||||
|
|
||||||
@ -22,24 +22,33 @@ _graceful_shutdown(){
|
|||||||
local QEMU_POWERDOWN_TIMEOUT="${QEMU_POWERDOWN_TIMEOUT:-120}"
|
local QEMU_POWERDOWN_TIMEOUT="${QEMU_POWERDOWN_TIMEOUT:-120}"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
echo "Received $1 signal.."
|
echo "Received $1 signal, shutting down..."
|
||||||
echo 0 > "${_QEMU_SHUTDOWN_COUNTER}"
|
echo 0 > "${_QEMU_SHUTDOWN_COUNTER}"
|
||||||
|
|
||||||
|
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||||
|
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
|
||||||
|
|
||||||
|
# Send shutdown command to guest agent tools instead via serial port
|
||||||
|
RESPONSE=$(curl -s -m 2 -S http://127.0.0.1:2210/write?command=6 2>&1)
|
||||||
|
|
||||||
|
if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then
|
||||||
|
|
||||||
|
echo "Could not send shutdown command to guest, error: $RESPONSE"
|
||||||
|
|
||||||
FILE="${IMG}/agent.ver"
|
FILE="${IMG}/agent.ver"
|
||||||
[ ! -f "$FILE" ] && echo "1" > "$FILE"
|
[ ! -f "$FILE" ] && echo "1" > "$FILE"
|
||||||
AGENT_VERSION=$(cat "${FILE}")
|
AGENT_VERSION=$(cat "${FILE}")
|
||||||
|
|
||||||
# Don't send the powerdown signal because Synology ignores it
|
|
||||||
# echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
|
|
||||||
|
|
||||||
if ((AGENT_VERSION < 2)); then
|
if ((AGENT_VERSION < 2)); then
|
||||||
echo "Please update the agent to allow gracefull shutdowns..."
|
echo "Please update the agent to allow gracefull shutdowns..."
|
||||||
pkill -f qemu-system-x86_64
|
pkill -f qemu-system-x86_64
|
||||||
else
|
else
|
||||||
# Send a NMI interrupt which will be detected by the agent
|
# Send a NMI interrupt which will be detected by the kernel
|
||||||
echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
|
echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do
|
while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do
|
||||||
|
|
||||||
# Increase the counter
|
# Increase the counter
|
||||||
@ -54,7 +63,7 @@ _graceful_shutdown(){
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Killing VM.."
|
echo "Quitting..."
|
||||||
echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null || true
|
echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null || true
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -8,6 +8,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"net/http"
|
||||||
|
"math/rand"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type REQ struct {
|
type REQ struct {
|
||||||
@ -35,16 +38,27 @@ var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version")
|
|||||||
var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp")
|
var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp")
|
||||||
var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864"
|
var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864"
|
||||||
|
|
||||||
|
var ApiPort = flag.String("api", ":2210", "API port")
|
||||||
var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address")
|
var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address")
|
||||||
|
|
||||||
|
var LastConnection net.Conn
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", home)
|
||||||
|
r.HandleFunc("/write", write)
|
||||||
|
go http.ListenAndServe(*ApiPort, r)
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", *ListenAddr)
|
listener, err := net.Listen("tcp", *ListenAddr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error listening", err.Error())
|
log.Println("Error listening", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Start listen on " + *ListenAddr)
|
log.Println("Start listen on " + *ListenAddr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -54,11 +68,15 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("New connection from %s\n", conn.RemoteAddr().String())
|
log.Printf("New connection from %s\n", conn.RemoteAddr().String())
|
||||||
|
|
||||||
go incoming_conn(conn)
|
go incoming_conn(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func incoming_conn(conn net.Conn) {
|
func incoming_conn(conn net.Conn) {
|
||||||
|
|
||||||
|
LastConnection = conn
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
len, err := conn.Read(buf)
|
len, err := conn.Read(buf)
|
||||||
@ -173,3 +191,73 @@ func process_req(buf []byte, conn net.Conn) {
|
|||||||
conn.Write(buf)
|
conn.Write(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(`{"status": "error", "data": null, "message": "No command specified"}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func write(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var commandID int
|
||||||
|
|
||||||
|
query := r.URL.Query()
|
||||||
|
commandID, err = strconv.Atoi(query.Get("command"))
|
||||||
|
|
||||||
|
if (err != nil || commandID < 1) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(`{"status": "error", "data": null, "message": "Invalid command ID"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send_command((int32)(commandID), 1) == false) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(`{"status": "error", "data": null, "message": "Failed to send command"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"status": "success", "data": null, "message": null}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_command(CommandID int32, SubCommand int32) bool {
|
||||||
|
|
||||||
|
var req REQ
|
||||||
|
|
||||||
|
req.CommandID = CommandID
|
||||||
|
req.SubCommand = SubCommand
|
||||||
|
|
||||||
|
req.IsReq = 1
|
||||||
|
req.IsResp = 0
|
||||||
|
req.ReqLength = 0
|
||||||
|
req.RespLength = 0
|
||||||
|
req.NeedResponse = 0
|
||||||
|
req.GuestID = 10000000
|
||||||
|
req.RandID = rand.Int63()
|
||||||
|
|
||||||
|
var buf = make([]byte, 0, 4096)
|
||||||
|
var writer = bytes.NewBuffer(buf)
|
||||||
|
|
||||||
|
// write to buf
|
||||||
|
binary.Write(writer, binary.LittleEndian, &req)
|
||||||
|
res := writer.Bytes()
|
||||||
|
|
||||||
|
// full fill 4096
|
||||||
|
buf = make([]byte, 4096, 4096)
|
||||||
|
copy(buf, res)
|
||||||
|
|
||||||
|
//log.Printf("Writing command %d\n", CommandID)
|
||||||
|
|
||||||
|
if (LastConnection == nil) { return false }
|
||||||
|
|
||||||
|
LastConnection.Write(buf)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user