mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
Merge remote-tracking branch 'manager/main'
Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
This commit is contained in:
@@ -5,14 +5,14 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "agent/agent.proto"
|
||||
- "agent/*.pb.go"
|
||||
- "manager/manager.proto"
|
||||
- "manager/*.pb.go"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "agent/agent.proto"
|
||||
- "agent/*.pb.go"
|
||||
- "manager/manager.proto"
|
||||
- "manager/*.pb.go"
|
||||
|
||||
jobs:
|
||||
checkproto:
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- name: Set up protoc
|
||||
run: |
|
||||
PROTOC_VERSION=23.3
|
||||
PROTOC_VERSION=24.2
|
||||
PROTOC_GEN_VERSION=v1.31.0
|
||||
PROTOC_GRPC_VERSION=v1.3.0
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: Set up Mainflux
|
||||
run: |
|
||||
# Rename .pb.go files to .pb.go.tmp to prevent conflicts
|
||||
for p in $(ls agent/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
mv $p $p.tmp
|
||||
done
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
make protoc
|
||||
|
||||
# Compare generated Go files with the original ones
|
||||
for p in $(ls agent/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
if ! cmp -s $p $p.tmp; then
|
||||
echo "Proto file and generated Go file $p are out of sync!"
|
||||
exit 1
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
build
|
||||
|
||||
build
|
||||
cmd/manager/img
|
||||
cmd/manager/iso
|
||||
cmd/manager/tmp
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright (c) 2019
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
agentgrpc "github.com/ultravioletrs/agent/pkg/clients/grpc"
|
||||
"github.com/ultravioletrs/manager/internal"
|
||||
"github.com/ultravioletrs/manager/internal/env"
|
||||
jaegerclient "github.com/ultravioletrs/manager/internal/jaeger"
|
||||
"github.com/ultravioletrs/manager/internal/server"
|
||||
grpcserver "github.com/ultravioletrs/manager/internal/server/grpc"
|
||||
httpserver "github.com/ultravioletrs/manager/internal/server/http"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
"github.com/ultravioletrs/manager/manager/api"
|
||||
managergrpc "github.com/ultravioletrs/manager/manager/api/grpc"
|
||||
httpapi "github.com/ultravioletrs/manager/manager/api/http"
|
||||
"github.com/ultravioletrs/manager/manager/qemu"
|
||||
"github.com/ultravioletrs/manager/manager/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "manager"
|
||||
envPrefixHTTP = "MANAGER_HTTP_"
|
||||
envPrefixGRPC = "MANAGER_GRPC_"
|
||||
envPrefixAgentGRPC = "AGENT_GRPC_"
|
||||
envPrefixQemu = "MANAGER_QEMU_"
|
||||
defSvcGRPCPort = "7001"
|
||||
defSvcHTTPPort = "9021"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MANAGER_LOG_LEVEL" envDefault:"info"`
|
||||
JaegerURL string `env:"MANAGER_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
|
||||
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
var cfg config
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
cfg.InstanceID, err = uuid.New().ID()
|
||||
if err != nil {
|
||||
logger.Fatal(fmt.Sprintf("Failed to generate instance ID: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
agentGRPCConfig := agentgrpc.Config{}
|
||||
if err := env.Parse(&agentGRPCConfig, env.Options{Prefix: envPrefixAgentGRPC}); err != nil {
|
||||
logger.Fatal(fmt.Sprintf("failed to load %s gRPC client configuration: %s", svcName, err))
|
||||
}
|
||||
agentGRPCClient, agentClient, err := agentgrpc.NewClient(agentGRPCConfig)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
defer agentGRPCClient.Close()
|
||||
|
||||
logger.Info(fmt.Sprintf("Successfully connected to agent grpc server at %s %s", agentGRPCConfig.URL, agentGRPCClient.Secure()))
|
||||
|
||||
qemuCfg := qemu.Config{}
|
||||
if err := env.Parse(&qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
|
||||
logger.Fatal(fmt.Sprintf("failed to load QEMU configuration: %s", err))
|
||||
}
|
||||
exe, args, err := qemu.ExecutableAndArgs(qemuCfg)
|
||||
if err != nil {
|
||||
logger.Fatal(fmt.Sprintf("failed to parse QEMU configuration: %s", err))
|
||||
}
|
||||
logger.Info(fmt.Sprintf("%s %s", exe, strings.Join(args, " ")))
|
||||
|
||||
svc := newService(agentClient, logger, tracer, qemuCfg)
|
||||
|
||||
var httpServerConfig = server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.Parse(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Fatal(fmt.Sprintf("failed to load %s gRPC server configuration: %s", svcName, err))
|
||||
}
|
||||
hs := httpserver.New(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, cfg.InstanceID), logger)
|
||||
|
||||
var grpcServerConfig = server.Config{Port: defSvcGRPCPort}
|
||||
if err := env.Parse(&grpcServerConfig, env.Options{Prefix: envPrefixGRPC}); err != nil {
|
||||
log.Fatalf("failed to load %s gRPC server configuration: %s", svcName, err.Error())
|
||||
}
|
||||
registerManagerServiceServer := func(srv *grpc.Server) {
|
||||
reflection.Register(srv)
|
||||
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(svc))
|
||||
}
|
||||
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerManagerServiceServer, logger)
|
||||
|
||||
g.Go(func() error {
|
||||
return hs.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return gs.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopHandler(ctx, cancel, logger, svcName, hs, gs)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
|
||||
err = internal.DeleteFilesInDir(qemuCfg.TmpFileLoc)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func newService(agent agent.AgentServiceClient, logger logger.Logger, tracer trace.Tracer, qemuCfg qemu.Config) manager.Service {
|
||||
svc := manager.New(agent, qemuCfg)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := internal.MakeMetrics(svcName, "api")
|
||||
svc = api.MetricsMiddleware(svc, counter, latency)
|
||||
svc = tracing.New(svc, tracer)
|
||||
|
||||
return svc
|
||||
}
|
||||
Executable
+270
@@ -0,0 +1,270 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# user changeable parameters
|
||||
#
|
||||
|
||||
HDA_FILE="cmd/manager/img/focal-server-cloudimg-amd64.qcow2"
|
||||
GUEST_SIZE_IN_MB="4096"
|
||||
SEV_GUEST="1"
|
||||
SMP_NCPUS="4"
|
||||
CONSOLE="serial"
|
||||
VNC_PORT=""
|
||||
USE_VIRTIO="1"
|
||||
|
||||
UEFI_BIOS_CODE="/usr/share/OVMF/OVMF_CODE.fd"
|
||||
UEFI_BIOS_VARS_ORIG="/usr/share/OVMF/OVMF_VARS.fd"
|
||||
UEFI_BIOS_VARS_COPY="cmd/manager/img/OVMF_VARS.fd"
|
||||
|
||||
CBITPOS=51
|
||||
HOST_HTTP_PORT=9301
|
||||
GUEST_HTTP_PORT=9031
|
||||
HOST_GRPC_PORT=7020
|
||||
GUEST_GRPC_PORT=7002
|
||||
|
||||
ENABLE_FILE_LOG="0"
|
||||
EXEC_QEMU_CMDLINE="0"
|
||||
|
||||
usage() {
|
||||
echo "$0 [options]"
|
||||
echo "Available <commands>:"
|
||||
echo " -hda hard disk ($HDA_FILE)"
|
||||
echo " -nosev disable sev support"
|
||||
echo " -mem guest memory"
|
||||
echo " -smp number of cpus"
|
||||
echo " -console display console to use (serial or gxl)"
|
||||
echo " -vnc VNC port to use"
|
||||
echo " -bios bios to use (default $UEFI_BIOS_CODE)"
|
||||
echo " -kernel kernel to use"
|
||||
echo " -initrd initrd to use"
|
||||
echo " -cdrom CDROM image"
|
||||
echo " -virtio use virtio devices"
|
||||
echo " -cbitpos location of the C-bit"
|
||||
echo " -hosthttp host http port"
|
||||
echo " -guesthttp guest http port"
|
||||
echo " -hostgrpc host grpc port"
|
||||
echo " -guestgrpc guest grpc port"
|
||||
echo " -origuefivars UEFI BIOS vars original file (default $UEFI_BIOS_VARS_ORIG)"
|
||||
echo " -copyuefivars UEFI BIOS vars copy file (default $UEFI_BIOS_VARS_COPY)"
|
||||
echo " -exec execute the QEMU command (default $EXEC_QEMU_CMDLINE)"
|
||||
echo " -filelog enable/disable QEMU cmd line file log (default: $ENABLE_FILE_LOG)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $1 != "" ]]; do
|
||||
case "$1" in
|
||||
-hda)
|
||||
HDA_FILE=${2}
|
||||
shift
|
||||
;;
|
||||
-nosev)
|
||||
SEV_GUEST="0"
|
||||
;;
|
||||
-mem)
|
||||
GUEST_SIZE_IN_MB=${2}
|
||||
shift
|
||||
;;
|
||||
-console)
|
||||
CONSOLE=${2}
|
||||
shift
|
||||
;;
|
||||
-smp)
|
||||
SMP_NCPUS=$2
|
||||
shift
|
||||
;;
|
||||
-vnc)
|
||||
VNC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-bios)
|
||||
UEFI_BIOS_CODE=$2
|
||||
shift
|
||||
;;
|
||||
-initrd)
|
||||
INITRD_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-kernel)
|
||||
KERNEL_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-cdrom)
|
||||
CDROM_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-virtio)
|
||||
USE_VIRTIO="1"
|
||||
;;
|
||||
-cbitpos)
|
||||
CBITPOS=$2
|
||||
shift
|
||||
;;
|
||||
-hosthttp)
|
||||
HOST_HTTP_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-guesthttp)
|
||||
GUEST_HTTP_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-guestgrpc)
|
||||
GUEST_GRPC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-hostgrpc)
|
||||
HOST_GRPC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-origuefivars)
|
||||
UEFI_BIOS_VARS_ORIG=$2
|
||||
shift
|
||||
;;
|
||||
-copyuefivars)
|
||||
UEFI_BIOS_VARS_COPY=$2
|
||||
shift
|
||||
;;
|
||||
-exec)
|
||||
EXEC_QEMU_CMDLINE="1"
|
||||
;;
|
||||
-filelog)
|
||||
ENABLE_FILE_LOG="1"
|
||||
;;
|
||||
*)
|
||||
usage;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
#
|
||||
# func definitions
|
||||
#
|
||||
|
||||
add_opts() {
|
||||
echo -n "$* " >> ${QEMU_CMDLINE}
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
if ! "$@"; then
|
||||
echo "Command '$*' failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# copy BIOS variables to new dest for VM use without modifying the original ones
|
||||
cp "$UEFI_BIOS_VARS_ORIG" "$UEFI_BIOS_VARS_COPY"
|
||||
|
||||
#
|
||||
# Qemu cmd line construction
|
||||
#
|
||||
|
||||
# we add all the qemu command line options into a file
|
||||
QEMU_CMDLINE=/tmp/cmdline.$$
|
||||
rm -rf ${QEMU_CMDLINE}
|
||||
|
||||
add_opts "$(which qemu-system-x86_64)"
|
||||
|
||||
# Basic virtual machine property
|
||||
add_opts "-enable-kvm -cpu EPYC -machine q35"
|
||||
|
||||
# add number of VCPUs
|
||||
[ -n "$SMP_NCPUS" ] && add_opts "-smp ${SMP_NCPUS},maxcpus=64"
|
||||
|
||||
# define guest memory
|
||||
add_opts "-m ${GUEST_SIZE_IN_MB}M,slots=5,maxmem=30G"
|
||||
|
||||
# The OVMF binary, including the non-volatile variable store, appears as a
|
||||
# "normal" qemu drive on the host side, and it is exposed to the guest as a
|
||||
# persistent flash device.
|
||||
add_opts "-drive if=pflash,format=raw,unit=0,file=${UEFI_BIOS_CODE},readonly=on"
|
||||
add_opts "-drive if=pflash,format=raw,unit=1,file=${UEFI_BIOS_VARS_COPY}"
|
||||
|
||||
# add CDROM if specified
|
||||
[ -n "$CDROM_FILE" ] && add_opts "-drive file=${CDROM_FILE},media=cdrom -boot d"
|
||||
|
||||
add_opts "-netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::$HOST_HTTP_PORT-:$GUEST_HTTP_PORT,hostfwd=tcp::$HOST_GRPC_PORT-:$GUEST_GRPC_PORT"
|
||||
add_opts "-device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile="
|
||||
|
||||
# If harddisk file is specified then add the HDD drive
|
||||
if [ -n "$HDA_FILE" ]; then
|
||||
if [ "$USE_VIRTIO" = "1" ]; then
|
||||
if [[ ${HDA_FILE} = *"qcow2" ]]; then
|
||||
add_opts "-drive file=${HDA_FILE},if=none,id=disk0,format=qcow2"
|
||||
else
|
||||
add_opts "-drive file=${HDA_FILE},if=none,id=disk0,format=raw"
|
||||
fi
|
||||
add_opts "-device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true"
|
||||
add_opts "-device scsi-hd,drive=disk0"
|
||||
else
|
||||
if [[ ${HDA_FILE} = *"qcow2" ]]; then
|
||||
add_opts "-drive file=${HDA_FILE},format=qcow2"
|
||||
else
|
||||
add_opts "-drive file=${HDA_FILE},format=raw"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# If this is SEV guest then add the encryption device objects to enable support
|
||||
if [ ${SEV_GUEST} = "1" ]; then
|
||||
add_opts "-object sev-guest,id=sev0,cbitpos=${CBITPOS},reduced-phys-bits=1"
|
||||
add_opts "-machine memory-encryption=sev0"
|
||||
fi
|
||||
|
||||
# if console is serial then disable graphical interface
|
||||
if [ "${CONSOLE}" = "serial" ]; then
|
||||
add_opts "-nographic"
|
||||
else
|
||||
add_opts "-vga ${CONSOLE}"
|
||||
fi
|
||||
|
||||
# if -kernel arg is specified then use the kernel provided in command line for boot
|
||||
if [ "${KERNEL_FILE}" != "" ]; then
|
||||
add_opts "-kernel $KERNEL_FILE"
|
||||
add_opts "-append \"console=ttyS0 earlyprintk=serial root=/dev/sda2\""
|
||||
[ -n "$INITRD_FILE" ] && add_opts "-initrd ${INITRD_FILE}"
|
||||
fi
|
||||
|
||||
# start vnc server
|
||||
[ -n "$VNC_PORT" ] && add_opts "-vnc :${VNC_PORT}" && echo "Starting VNC on port ${VNC_PORT}"
|
||||
|
||||
# start monitor on pty
|
||||
add_opts "-monitor pty"
|
||||
|
||||
#
|
||||
# Qemu cmd line log
|
||||
#
|
||||
|
||||
# Set the log file path if ENABLE_FILE_LOG is 1
|
||||
if [ "$ENABLE_FILE_LOG" = "1" ]; then
|
||||
LOG_FILE=$(pwd)/stdout.log
|
||||
|
||||
# Save the command line args into log file
|
||||
cat "$QEMU_CMDLINE" > "$LOG_FILE"
|
||||
echo >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Log the command line to the console
|
||||
cat "$QEMU_CMDLINE"
|
||||
|
||||
#
|
||||
# Qemu cmd line execution
|
||||
#
|
||||
|
||||
if [[ "${EXEC_QEMU_CMDLINE}" = "0" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# map CTRL-C to CTRL ]
|
||||
echo "Mapping CTRL-C to CTRL-]"
|
||||
stty intr ^]
|
||||
|
||||
echo "Launching VM ..."
|
||||
if [ "$ENABLE_FILE_LOG" = "1" ]; then
|
||||
bash ${QEMU_CMDLINE} 2>&1 | tee -a "${LOG_FILE}"
|
||||
else
|
||||
bash ${QEMU_CMDLINE} 2>&1
|
||||
fi
|
||||
|
||||
# restore the mapping
|
||||
stty intr ^c
|
||||
|
||||
rm -rf ${QEMU_CMDLINE}
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set your default values for sudo and sev
|
||||
sudo_option=false
|
||||
sev_option=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
--sudo)
|
||||
sudo_option=true
|
||||
shift
|
||||
;;
|
||||
--sev)
|
||||
sev_option=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $key"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
build_qemu_command() {
|
||||
local qemu_command="/usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 2048M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=$MANAGER_QEMU_OVMF_CODE_FILE,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -nographic -monitor pty"
|
||||
|
||||
if [ "$sev_option" = true ]; then
|
||||
qemu_command="$qemu_command -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1 -machine memory-encryption=sev0"
|
||||
fi
|
||||
|
||||
echo "$qemu_command"
|
||||
}
|
||||
|
||||
if [ ! -f "img/OVMF_VARS.fd" ]; then
|
||||
cp "$MANAGER_QEMU_OVMF_VARS_FILE" "img/OVMF_VARS.fd"
|
||||
echo "Copied $MANAGER_QEMU_OVMF_VARS_FILE to img/OVMF_VARS.fd"
|
||||
else
|
||||
echo "img/OVMF_VARS.fd already exists. No need to copy."
|
||||
fi
|
||||
|
||||
echo "Launching VM ..."
|
||||
|
||||
qemu_command=$(build_qemu_command)
|
||||
echo "$qemu_command"
|
||||
|
||||
echo "Mapping CTRL-C to CTRL-]"
|
||||
stty intr ^]
|
||||
|
||||
if [ "$sudo_option" = true ]; then
|
||||
# Split the command and arguments into an array; << operator is known as a "here string"
|
||||
IFS=" " read -r -a qemu_command_array <<< "$qemu_command"
|
||||
# Treat each element in the array as a separate word, preserving spaces within each element
|
||||
sudo "${qemu_command_array[@]}"
|
||||
else
|
||||
$qemu_command
|
||||
fi
|
||||
|
||||
# Restore the mapping
|
||||
stty intr ^c
|
||||
@@ -0,0 +1,62 @@
|
||||
<domain type="kvm">
|
||||
<name>QEmu-alpine-standard-x86_64</name>
|
||||
<uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
|
||||
<metadata>
|
||||
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
|
||||
<libosinfo:os id="http://alpinelinux.org/alpinelinux/3.15"/>
|
||||
</libosinfo:libosinfo>
|
||||
</metadata>
|
||||
<memory unit="KiB">4194304</memory>
|
||||
<currentMemory unit="KiB">4194304</currentMemory>
|
||||
<vcpu placement="static">1</vcpu>
|
||||
<os>
|
||||
<type arch="x86_64" machine="q35">hvm</type>
|
||||
<bootmenu enable="yes"/>
|
||||
<loader readonly="yes" type="pflash">/usr/share/OVMF/OVMF_CODE.fd</loader>
|
||||
<nvram template='/usr/share/OVMF/OVMF_VARS.fd'>./img/OVMF_VARS.fd</nvram>
|
||||
<!-- <boot dev='hd'/> -->
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
<apic/>
|
||||
<vmport state="off"/>
|
||||
</features>
|
||||
<cpu mode="host-passthrough" check="none" migratable="on"/>
|
||||
<clock offset="utc">
|
||||
<timer name="rtc" tickpolicy="catchup"/>
|
||||
<timer name="pit" tickpolicy="delay"/>
|
||||
<timer name="hpet" present="no"/>
|
||||
</clock>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>destroy</on_crash>
|
||||
<pm>
|
||||
<suspend-to-mem enabled="no"/>
|
||||
<suspend-to-disk enabled="no"/>
|
||||
</pm>
|
||||
<devices>
|
||||
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
||||
<disk type="file" device="disk">
|
||||
<driver name="qemu" type="qcow2" discard="unmap"/>
|
||||
<source file="./img/focal-server-cloudimg-amd64.qcow2"/>
|
||||
<target dev="vda" bus="virtio"/>
|
||||
<address type="pci" domain="0x0000" bus="0x04" slot="0x00" function="0x0"/>
|
||||
<boot order="1"/>
|
||||
</disk>
|
||||
<graphics type="spice" autoport="yes">
|
||||
<listen type="address"/>
|
||||
<image compression="off"/>
|
||||
</graphics>
|
||||
<video>
|
||||
<model type="qxl" ram="65536" vram="65536" vgamem="16384" heads="1" primary="yes"/>
|
||||
<address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
|
||||
</video>
|
||||
<interface type="network">
|
||||
<mac address="52:54:00:03:7b:5f"/>
|
||||
<source network="default"/>
|
||||
<model type="virtio"/>
|
||||
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
|
||||
</interface>
|
||||
</devices>
|
||||
|
||||
</domain>
|
||||
@@ -0,0 +1,6 @@
|
||||
<pool type="dir">
|
||||
<name>virtimages</name>
|
||||
<target>
|
||||
<path>./img</path>
|
||||
</target>
|
||||
</pool>
|
||||
@@ -0,0 +1,15 @@
|
||||
<volume>
|
||||
<name>boot.img</name>
|
||||
<allocation>0</allocation>
|
||||
<capacity unit="G">1</capacity>
|
||||
<target>
|
||||
<format type="qcow2"/>
|
||||
<path>./img/boot.img</path>
|
||||
<permissions>
|
||||
<owner>107</owner>
|
||||
<group>107</group>
|
||||
<mode>0744</mode>
|
||||
<label>virt_image_t</label>
|
||||
</permissions>
|
||||
</target>
|
||||
</volume>
|
||||
@@ -1,16 +1,17 @@
|
||||
module github.com/ultravioletrs/agent
|
||||
module github.com/ultravioletrs/manager
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519
|
||||
github.com/go-kit/kit v0.12.0
|
||||
github.com/go-zoo/bone v1.3.0
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/mainflux/mainflux v0.0.0-20230726142711-2b78902e0170
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/ultravioletrs/agent v0.0.0-20230905145147-a3c466449737
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0
|
||||
@@ -24,7 +25,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.20.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
@@ -32,8 +32,7 @@ require (
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
|
||||
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
|
||||
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
|
||||
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519 h1:OpkN/n40cmKenDQS+IOAeW9DLhYy4DADSeZnouCEV/E=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519/go.mod h1:WyJJyfmJ0gWJvjV+ZH4DOgtOYZc1KOvYyBXWCLKxsUU=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
@@ -36,13 +39,12 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/mainflux/mainflux v0.0.0-20230726142711-2b78902e0170 h1:lMvLFbtNJdUufGGYaC/MrNH+B1uEYs4LWstTjphWnbc=
|
||||
github.com/mainflux/mainflux v0.0.0-20230726142711-2b78902e0170/go.mod h1:FoeJ13mrfikrsFDW6bOb3C44D5gZ5m9Jt249G1sLKq0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
@@ -51,15 +53,20 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/ultravioletrs/agent v0.0.0-20230905145147-a3c466449737 h1:+ioOzCO19gQKHyqe3IiGmlE1ppFWGMdPTAZaTzrqEp8=
|
||||
github.com/ultravioletrs/agent v0.0.0-20230905145147-a3c466449737/go.mod h1:1l4lWaw+otpIs1vXItU3x162wptVt9lSZMh9sxl1+ng=
|
||||
github.com/ultravioletrs/agent v0.0.0-20230921133856-4f0580560012/go.mod h1:tEihHfsPvZt7CRlg2g4jrCqELNs3ZHjLlg/F4XSgmEs=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 h1:ZOLJc06r4CB42laIXg/7udr0pbZyuAihN10A/XuiQRY=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
|
||||
@@ -76,16 +83,38 @@ go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiM
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e h1:S83+ibolgyZ0bqz7KEsUOPErxcv4VzlszxY+31OfB/E=
|
||||
@@ -97,5 +126,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExeShCmdStdout executes a shell command capturing the standard output.
|
||||
func ExeShCmdStdout(command string, args ...string) (string, error) {
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
|
||||
// Capture stdout and stderr using buffers
|
||||
cmd.Stdout = io.MultiWriter(&stdoutBuf, os.Stdout)
|
||||
cmd.Stderr = io.MultiWriter(&stderrBuf, os.Stderr)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing command '%s': %s", cmd.String(), err)
|
||||
}
|
||||
|
||||
return stdoutBuf.String(), nil
|
||||
}
|
||||
|
||||
// ExtractCmdAndArgs extracts the command and its arguments from the output string.
|
||||
func ExtractCmdAndArgs(cmdLine string, sudo bool) (string, []string) {
|
||||
lines := strings.Split(cmdLine, "\n")
|
||||
if len(lines) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := strings.Fields(lines[0])
|
||||
if len(parts) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if sudo {
|
||||
parts = append([]string{"sudo"}, parts...)
|
||||
}
|
||||
|
||||
cmd := parts[0]
|
||||
args := parts[1:]
|
||||
|
||||
return cmd, args
|
||||
}
|
||||
|
||||
// RunCmdOutput runs the specified command and returns its standard output as a string.
|
||||
func RunCmdOutput(command string, args ...string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing command '%s': %s", cmd.String(), err)
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// RunCmdStart starts the specified command and returns the *exec.Cmd for the running process.
|
||||
func RunCmdStart(command string, args ...string) (*exec.Cmd, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error starting command '%s': %s", cmd.String(), err)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CopyFile copies a file from srcPath to dstPath.
|
||||
func CopyFile(srcPath, dstPath string) error {
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFilesInDir deletes all files in the directory dirPath.
|
||||
func DeleteFilesInDir(dirPath string) error {
|
||||
files, err := filepath.Glob(filepath.Join(dirPath, "*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err := os.Remove(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/ultravioletrs/agent/internal/server"
|
||||
"github.com/ultravioletrs/manager/internal/server"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
@@ -32,7 +32,7 @@ type BaseServer struct {
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func stopAllServers(servers ...Server) error {
|
||||
func stopAllServer(servers ...Server) error {
|
||||
var errs []error
|
||||
for _, server := range servers {
|
||||
if err := server.Stop(); err != nil {
|
||||
@@ -54,7 +54,7 @@ func StopHandler(ctx context.Context, cancel context.CancelFunc, logger logger.L
|
||||
select {
|
||||
case sig := <-c:
|
||||
defer cancel()
|
||||
err = stopAllServers(servers...)
|
||||
err = stopAllServer(servers...)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service error during shutdown: %v", svcName, err))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package libvirt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
libvirt "github.com/digitalocean/go-libvirt"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
)
|
||||
|
||||
func Connect(logger logger.Logger) *libvirt.Libvirt {
|
||||
// This dials libvirt on the local machine, but you can substitute the first
|
||||
// two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
|
||||
// a remote machine.
|
||||
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to dial libvirt: %v", err)
|
||||
}
|
||||
|
||||
l := libvirt.New(c)
|
||||
if err := l.Connect(); err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
v, err := l.Version()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to retrieve libvirt version: %v", err))
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Retrieved libvirt version: %s", v))
|
||||
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to retrieve domains: %v", err))
|
||||
}
|
||||
|
||||
for _, d := range domains {
|
||||
logger.Info(fmt.Sprintf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID))
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package libvirt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
libvirt "github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`'([^']*)'`)
|
||||
|
||||
func CreateDomain(ctx context.Context, libvirt *libvirt.Libvirt, poolXML, volXML, domXML string) (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
poolStr, err := readXMLFile(poolXML, "pool.xml")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
poolStr = replaceSubstring(poolStr, "./", wd+"/")
|
||||
|
||||
volStr, err := readXMLFile(volXML, "vol.xml")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
volStr = replaceSubstring(volStr, "./", wd+"/")
|
||||
|
||||
domStr, err := readXMLFile(domXML, "dom.xml")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
domStr = replaceSubstring(domStr, "./", wd+"/")
|
||||
|
||||
dom, err := createDomain(libvirt, poolStr, volStr, domStr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create domain: %s", err)
|
||||
}
|
||||
|
||||
return dom.Name, nil
|
||||
}
|
||||
|
||||
func createDomain(libvirtConn *libvirt.Libvirt, poolXML string, volXML string, domXML string) (libvirt.Domain, error) {
|
||||
pool, err := libvirtConn.StoragePoolCreateXML(poolXML, 0)
|
||||
_ = pool
|
||||
if err != nil {
|
||||
lvErr := err.(libvirt.Error)
|
||||
if lvErr.Code == 9 {
|
||||
name, err := entityName(lvErr.Message)
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
pool, err = libvirtConn.StoragePoolLookupByName(name)
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
goto pool_exists
|
||||
}
|
||||
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
pool_exists:
|
||||
|
||||
_, err = libvirtConn.StorageVolCreateXML(pool, volXML, 0)
|
||||
if err != nil {
|
||||
lvErr := err.(libvirt.Error)
|
||||
if lvErr.Code == 90 {
|
||||
name, err := entityName(lvErr.Message)
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
_, err = libvirtConn.StorageVolLookupByName(pool, name)
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
goto vol_exists
|
||||
}
|
||||
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
vol_exists:
|
||||
|
||||
dom, err := libvirtConn.DomainDefineXMLFlags(domXML, 0)
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
err = libvirtConn.DomainCreate(dom)
|
||||
if err != nil {
|
||||
lvErr := err.(libvirt.Error)
|
||||
if lvErr.Code == 55 {
|
||||
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
// extra flags; not used yet, so callers should always pass 0
|
||||
current, err := libvirtConn.DomainSnapshotCurrent(dom, 0)
|
||||
if err != nil {
|
||||
lvErr := err.(libvirt.Error)
|
||||
if lvErr.Code == 72 {
|
||||
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
err = libvirtConn.DomainRevertToSnapshot(current, uint32(libvirt.DomainSnapshotRevertRunning))
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
}
|
||||
|
||||
return dom, nil
|
||||
}
|
||||
|
||||
func entityName(msg string) (string, error) {
|
||||
match := re.FindStringSubmatch(msg)
|
||||
if len(match) < 1 {
|
||||
return "", errors.New("entity not found")
|
||||
}
|
||||
|
||||
return match[1], nil
|
||||
}
|
||||
|
||||
func readXMLFile(filename string, defaultFilename string) (string, error) {
|
||||
if filename == "" {
|
||||
filename = "./xml/" + defaultFilename
|
||||
}
|
||||
|
||||
xmlBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read XML file: %s", err)
|
||||
}
|
||||
|
||||
return string(xmlBytes), nil
|
||||
}
|
||||
|
||||
func replaceSubstring(xml, substring, replacement string) string {
|
||||
// Split the file text into lines
|
||||
lines := strings.Split(xml, "\n")
|
||||
|
||||
// Create a variable to hold the resulting string
|
||||
var result strings.Builder
|
||||
|
||||
// Iterate over each line
|
||||
for _, line := range lines {
|
||||
// Replace the substring with the replacement
|
||||
newLine := strings.ReplaceAll(line, substring, replacement)
|
||||
|
||||
// Append the modified line to the resulting string
|
||||
result.WriteString(newLine)
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Manager
|
||||
|
||||
Manager service provides a barebones HTTP and gRPC API and Service interface implementation for the development of the manager service.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------ | -------------------------------------------------------- | --------------------------------- |
|
||||
| MANAGER_LOG_LEVEL | Log level for manager service (debug, info, warn, error) | info |
|
||||
| MANAGER_HTTP_HOST | Manager service HTTP host | |
|
||||
| MANAGER_HTTP_PORT | Manager service HTTP port | 9021 |
|
||||
| MANAGER_HTTP_SERVER_CERT | Path to server certificate in pem format | |
|
||||
| MANAGER_HTTP_SERVER_KEY | Path to server key in pem format | |
|
||||
| MANAGER_GRPC_HOST | Manager service gRPC host | |
|
||||
| MANAGER_GRPC_PORT | Manager service gRPC port | 7001 |
|
||||
| MANAGER_GRPC_SERVER_CERT | Path to server certificate in pem format | |
|
||||
| MANAGER_GRPC_SERVER_KEY | Path to server key in pem format | |
|
||||
| AGENT_GRPC_URL | Agent service gRPC URL | localhost:7002 |
|
||||
| AGENT_GRPC_TIMEOUT | Agent service gRPC timeout | 1s |
|
||||
| AGENT_GRPC_CA_CERTS | Agent service gRPC CA certificates | |
|
||||
| AGENT_GRPC_CLIENT_TLS | Agent service gRPC client TLS | false |
|
||||
| MANAGER_JAEGER_URL | Jaeger server URL | http://localhost:14268/api/traces |
|
||||
| MANAGER_INSTANCE_ID | Manager service instance ID | |
|
||||
|
||||
## Deployment
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
```bash
|
||||
# download the latest version of the service
|
||||
go get github.com/ultravioletrs/manager
|
||||
|
||||
cd $GOPATH/src/github.com/ultravioletrs/manager
|
||||
|
||||
# compile the manager
|
||||
make manager
|
||||
|
||||
# copy binary to bin
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For more information about service capabilities and its usage, please check out the [README documentation](../README.md).
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains API-related concerns: endpoint definitions, middlewares
|
||||
// and all resource representations.
|
||||
package api
|
||||
@@ -0,0 +1,75 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const svcName = "manager.ManagerService"
|
||||
|
||||
type grpcClient struct {
|
||||
run endpoint.Endpoint
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClient returns new gRPC client instance.
|
||||
func NewClient(conn *grpc.ClientConn, timeout time.Duration) manager.ManagerServiceClient {
|
||||
return &grpcClient{
|
||||
run: kitgrpc.NewClient(
|
||||
conn,
|
||||
svcName,
|
||||
"Run",
|
||||
encodeRunRequest,
|
||||
decodeRunResponse,
|
||||
manager.RunResponse{},
|
||||
).Endpoint(),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRunRequest is a transport/grpc.EncodeRequestFunc that
|
||||
// converts a user-domain runReq to a gRPC request.
|
||||
func encodeRunRequest(_ context.Context, request interface{}) (interface{}, error) {
|
||||
req, ok := request.(runReq)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid request type: %T", request)
|
||||
}
|
||||
return &manager.RunRequest{
|
||||
Computation: req.Computation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decodeRunResponse is a transport/grpc.DecodeResponseFunc that
|
||||
// converts a gRPC RunResponse to a user-domain response.
|
||||
func decodeRunResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
|
||||
response, ok := grpcResponse.(*manager.RunResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
|
||||
}
|
||||
return runRes{
|
||||
ID: response.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client grpcClient) Run(ctx context.Context, req *manager.RunRequest, _ ...grpc.CallOption) (*manager.RunResponse, error) {
|
||||
ctx, close := context.WithTimeout(ctx, client.timeout)
|
||||
defer close()
|
||||
|
||||
runReq := runReq{
|
||||
Computation: req.GetComputation(),
|
||||
}
|
||||
|
||||
res, err := client.run(ctx, runReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runRes := res.(runRes)
|
||||
return &manager.RunResponse{ID: runRes.ID}, nil
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package grpc contains implementation of kit service gRPC API.
|
||||
package grpc
|
||||
@@ -0,0 +1,25 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
)
|
||||
|
||||
func runEndpoint(svc manager.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(runReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return runRes{}, err
|
||||
}
|
||||
|
||||
id, err := svc.Run(ctx, req.Computation)
|
||||
if err != nil {
|
||||
return runRes{}, err
|
||||
}
|
||||
|
||||
return runRes{ID: id}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package grpc
|
||||
|
||||
import "github.com/ultravioletrs/manager/manager"
|
||||
|
||||
type runReq struct {
|
||||
Computation []byte `json:"computation,omitempty"`
|
||||
}
|
||||
|
||||
func (req runReq) validate() error {
|
||||
if len(req.Computation) == 0 {
|
||||
return manager.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package grpc
|
||||
|
||||
type runRes struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
)
|
||||
|
||||
type grpcServer struct {
|
||||
run kitgrpc.Handler
|
||||
manager.UnimplementedManagerServiceServer
|
||||
}
|
||||
|
||||
// NewServer returns new AuthServiceServer instance.
|
||||
func NewServer(svc manager.Service) manager.ManagerServiceServer {
|
||||
return &grpcServer{
|
||||
run: kitgrpc.NewServer(
|
||||
runEndpoint(svc),
|
||||
decodeRunRequest,
|
||||
encodeRunResponse,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRunRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*manager.RunRequest)
|
||||
return runReq{
|
||||
Computation: req.GetComputation(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeRunResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(runRes)
|
||||
return &manager.RunResponse{
|
||||
ID: res.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Run(ctx context.Context, req *manager.RunRequest) (*manager.RunResponse, error) {
|
||||
_, res, err := s.run.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr := res.(*manager.RunResponse)
|
||||
return rr, nil
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package http contains implementation of kit service HTTP API.
|
||||
package http
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
)
|
||||
|
||||
func runEndpoint(svc manager.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(runReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call the Run method on the service
|
||||
runID, err := svc.Run(ctx, req.Computation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the response
|
||||
res := runRes{
|
||||
ID: runID,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import "github.com/ultravioletrs/manager/manager"
|
||||
|
||||
var _ apiReq = (*runReq)(nil)
|
||||
|
||||
type apiReq interface {
|
||||
validate() error
|
||||
}
|
||||
|
||||
type runReq struct {
|
||||
Computation []byte `json:"computation,omitempty"`
|
||||
}
|
||||
|
||||
func (req runReq) validate() error {
|
||||
if len(req.Computation) == 0 {
|
||||
return manager.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
var _ mainflux.Response = (*runRes)(nil)
|
||||
|
||||
type runRes struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (res runRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res runRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res runRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/go-zoo/bone"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
|
||||
var (
|
||||
errUnsupportedContentType = errors.New("unsupported content type")
|
||||
errInvalidQueryParams = errors.New("invalid query params")
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(svc manager.Service, instanceID string) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(encodeError),
|
||||
}
|
||||
|
||||
r := bone.New()
|
||||
|
||||
r.Post("/run", otelhttp.NewHandler(kithttp.NewServer(
|
||||
runEndpoint(svc),
|
||||
decodeRun,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
), "run"))
|
||||
|
||||
r.GetFunc("/health", mainflux.Health("manager", instanceID))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func decodeRun(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var req runReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
if ar, ok := response.(mainflux.Response); ok {
|
||||
for k, v := range ar.Headers() {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
w.WriteHeader(ar.Code())
|
||||
|
||||
if ar.Empty() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
switch err {
|
||||
case manager.ErrMalformedEntity:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case manager.ErrNotFound:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
case manager.ErrUnauthorizedAccess:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
case errUnsupportedContentType:
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
case errInvalidQueryParams:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case io.ErrUnexpectedEOF:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case io.EOF:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
default:
|
||||
switch err.(type) {
|
||||
case *json.SyntaxError:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case *json.UnmarshalTypeError:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
default:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
// +build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
)
|
||||
|
||||
var _ manager.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
svc manager.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the core service.
|
||||
func LoggingMiddleware(svc manager.Service, logger log.Logger) manager.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Run(ctx context.Context, computation []byte) (id string, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method Run for computation took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s with ID: %s", message, id))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Run(ctx, computation)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !test
|
||||
// +build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
)
|
||||
|
||||
var _ manager.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
svc manager.Service
|
||||
}
|
||||
|
||||
// MetricsMiddleware instruments core service by tracking request count and
|
||||
// latency.
|
||||
func MetricsMiddleware(svc manager.Service, counter metrics.Counter, latency metrics.Histogram) manager.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
svc: svc,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Run(ctx context.Context, computation []byte) (string, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "Run").Add(1)
|
||||
ms.latency.With("method", "Run").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Run(ctx, computation)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package things contains the domain concept definitions needed to support
|
||||
// Mainflux manager service functionality.
|
||||
|
||||
package manager
|
||||
@@ -0,0 +1,211 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v4.24.2
|
||||
// source: manager/manager.proto
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type RunRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Computation []byte `protobuf:"bytes,1,opt,name=computation,proto3" json:"computation,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RunRequest) Reset() {
|
||||
*x = RunRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_manager_manager_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RunRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RunRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_manager_manager_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RunRequest) Descriptor() ([]byte, []int) {
|
||||
return file_manager_manager_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetComputation() []byte {
|
||||
if x != nil {
|
||||
return x.Computation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RunResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RunResponse) Reset() {
|
||||
*x = RunResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_manager_manager_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RunResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RunResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_manager_manager_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RunResponse) Descriptor() ([]byte, []int) {
|
||||
return file_manager_manager_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RunResponse) GetID() string {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_manager_manager_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_manager_manager_proto_rawDesc = []byte{
|
||||
0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20,
|
||||
0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x22, 0x1d, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x32,
|
||||
0x44, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0x32, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_manager_manager_proto_rawDescOnce sync.Once
|
||||
file_manager_manager_proto_rawDescData = file_manager_manager_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_manager_manager_proto_rawDescGZIP() []byte {
|
||||
file_manager_manager_proto_rawDescOnce.Do(func() {
|
||||
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_manager_manager_proto_rawDescData)
|
||||
})
|
||||
return file_manager_manager_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_manager_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_manager_manager_proto_goTypes = []interface{}{
|
||||
(*RunRequest)(nil), // 0: manager.RunRequest
|
||||
(*RunResponse)(nil), // 1: manager.RunResponse
|
||||
}
|
||||
var file_manager_manager_proto_depIdxs = []int32{
|
||||
0, // 0: manager.ManagerService.Run:input_type -> manager.RunRequest
|
||||
1, // 1: manager.ManagerService.Run:output_type -> manager.RunResponse
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_manager_manager_proto_init() }
|
||||
func file_manager_manager_proto_init() {
|
||||
if File_manager_manager_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_manager_manager_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RunRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_manager_manager_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RunResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_manager_manager_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_manager_manager_proto_goTypes,
|
||||
DependencyIndexes: file_manager_manager_proto_depIdxs,
|
||||
MessageInfos: file_manager_manager_proto_msgTypes,
|
||||
}.Build()
|
||||
File_manager_manager_proto = out.File
|
||||
file_manager_manager_proto_rawDesc = nil
|
||||
file_manager_manager_proto_goTypes = nil
|
||||
file_manager_manager_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package manager;
|
||||
|
||||
option go_package = "./manager";
|
||||
|
||||
service ManagerService {
|
||||
rpc Run(RunRequest) returns (RunResponse) {}
|
||||
}
|
||||
|
||||
message RunRequest { bytes computation = 1; }
|
||||
|
||||
message RunResponse { string ID = 1; }
|
||||
@@ -0,0 +1,109 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v4.24.2
|
||||
// source: manager/manager.proto
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
ManagerService_Run_FullMethodName = "/manager.ManagerService/Run"
|
||||
)
|
||||
|
||||
// ManagerServiceClient is the client API for ManagerService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ManagerServiceClient interface {
|
||||
Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error)
|
||||
}
|
||||
|
||||
type managerServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewManagerServiceClient(cc grpc.ClientConnInterface) ManagerServiceClient {
|
||||
return &managerServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *managerServiceClient) Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error) {
|
||||
out := new(RunResponse)
|
||||
err := c.cc.Invoke(ctx, ManagerService_Run_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ManagerServiceServer is the server API for ManagerService service.
|
||||
// All implementations must embed UnimplementedManagerServiceServer
|
||||
// for forward compatibility
|
||||
type ManagerServiceServer interface {
|
||||
Run(context.Context, *RunRequest) (*RunResponse, error)
|
||||
mustEmbedUnimplementedManagerServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedManagerServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedManagerServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedManagerServiceServer) Run(context.Context, *RunRequest) (*RunResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Run not implemented")
|
||||
}
|
||||
func (UnimplementedManagerServiceServer) mustEmbedUnimplementedManagerServiceServer() {}
|
||||
|
||||
// UnsafeManagerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ManagerServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeManagerServiceServer interface {
|
||||
mustEmbedUnimplementedManagerServiceServer()
|
||||
}
|
||||
|
||||
func RegisterManagerServiceServer(s grpc.ServiceRegistrar, srv ManagerServiceServer) {
|
||||
s.RegisterService(&ManagerService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _ManagerService_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RunRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagerServiceServer).Run(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagerService_Run_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagerServiceServer).Run(ctx, req.(*RunRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ManagerService_ServiceDesc is the grpc.ServiceDesc for ManagerService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var ManagerService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "manager.ManagerService",
|
||||
HandlerType: (*ManagerServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Run",
|
||||
Handler: _ManagerService_Run_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "manager/manager.proto",
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package qemu
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MemoryConfig struct {
|
||||
Size string `env:"MEMORY_SIZE" envDefault:"2048M"`
|
||||
Slots int `env:"MEMORY_SLOTS" envDefault:"5"`
|
||||
Max string `env:"MAX_MEMORY" envDefault:"30G"`
|
||||
}
|
||||
|
||||
type OVMFCodeConfig struct {
|
||||
If string `env:"OVMF_CODE_IF" envDefault:"pflash"`
|
||||
Format string `env:"OVMF_CODE_FORMAT" envDefault:"raw"`
|
||||
Unit int `env:"OVMF_CODE_UNIT" envDefault:"0"`
|
||||
File string `env:"OVMF_CODE_FILE" envDefault:"/usr/share/OVMF/OVMF_CODE.fd"`
|
||||
ReadOnly string `env:"OVMF_CODE_READONLY" envDefault:"on"`
|
||||
}
|
||||
|
||||
type OVMFVarsConfig struct {
|
||||
If string `env:"OVMF_VARS_IF" envDefault:"pflash"`
|
||||
Format string `env:"OVMF_VARS_FORMAT" envDefault:"raw"`
|
||||
Unit int `env:"OVMF_VARS_UNIT" envDefault:"1"`
|
||||
File string `env:"OVMF_VARS_FILE" envDefault:"/usr/share/OVMF/OVMF_VARS.fd"`
|
||||
}
|
||||
|
||||
type NetDevConfig struct {
|
||||
ID string `env:"NETDEV_ID" envDefault:"vmnic"`
|
||||
HostFwd1 int `env:"HOST_FWD_1" envDefault:"2222"`
|
||||
GuestFwd1 int `env:"GUEST_FWD_1" envDefault:"22"`
|
||||
HostFwd2 int `env:"HOST_FWD_2" envDefault:"9301"`
|
||||
GuestFwd2 int `env:"GUEST_FWD_2" envDefault:"9031"`
|
||||
HostFwd3 int `env:"HOST_FWD_3" envDefault:"7020"`
|
||||
GuestFwd3 int `env:"GUEST_FWD_3" envDefault:"7002"`
|
||||
}
|
||||
|
||||
type VirtioNetPciConfig struct {
|
||||
DisableLegacy string `env:"VIRTIO_NET_PCI_DISABLE_LEGACY" envDefault:"on"`
|
||||
IOMMUPlatform bool `env:"VIRTIO_NET_PCI_IOMMU_PLATFORM" envDefault:"true"`
|
||||
ROMFile string `env:"VIRTIO_NET_PCI_ROMFILE"`
|
||||
}
|
||||
|
||||
type DiskImgConfig struct {
|
||||
File string `env:"DISK_IMG_FILE" envDefault:"img/focal-server-cloudimg-amd64.img"`
|
||||
If string `env:"DISK_IMG_IF" envDefault:"none"`
|
||||
ID string `env:"DISK_IMG_ID" envDefault:"disk0"`
|
||||
Format string `env:"DISK_IMG_FORMAT" envDefault:"qcow2"`
|
||||
}
|
||||
|
||||
type VirtioScsiPciConfig struct {
|
||||
ID string `env:"VIRTIO_SCSI_PCI_ID" envDefault:"scsi"`
|
||||
DisableLegacy string `env:"VIRTIO_SCSI_PCI_DISABLE_LEGACY" envDefault:"on"`
|
||||
IOMMUPlatform bool `env:"VIRTIO_SCSI_PCI_IOMMU_PLATFORM" envDefault:"true"`
|
||||
}
|
||||
|
||||
type SevConfig struct {
|
||||
ID string `env:"SEV_ID" envDefault:"sev0"`
|
||||
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
|
||||
ReducedPhysBits int `env:"SEV_REDUCED_PHYS_BITS" envDefault:"1"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
TmpFileLoc string `env:"TMP_FILE_LOC" envDefault:"tmp"`
|
||||
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
|
||||
EnableSEV bool `env:"ENABLE_SEV" envDefault:"true"`
|
||||
|
||||
EnableKVM bool `env:"ENABLE_KVM" envDefault:"true"`
|
||||
|
||||
// machine, CPU, RAM
|
||||
Machine string `env:"MACHINE" envDefault:"q35"`
|
||||
CPU string `env:"CPU" envDefault:"EPYC"`
|
||||
SmpCount int `env:"SMP_COUNT" envDefault:"4"`
|
||||
MaxCpus int `env:"SMP_MAXCPUS" envDefault:"64"`
|
||||
MemoryConfig
|
||||
|
||||
// OVMF
|
||||
OVMFCodeConfig
|
||||
OVMFVarsConfig
|
||||
|
||||
// network
|
||||
NetDevConfig
|
||||
VirtioNetPciConfig
|
||||
|
||||
// disk
|
||||
VirtioScsiPciConfig
|
||||
DiskImgConfig
|
||||
|
||||
// SEV
|
||||
SevConfig
|
||||
|
||||
// display
|
||||
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
|
||||
Monitor string `env:"MONITOR" envDefault:"pty"`
|
||||
}
|
||||
|
||||
func constructQemuArgs(config Config) []string {
|
||||
args := []string{}
|
||||
|
||||
// virtualization
|
||||
if config.EnableKVM {
|
||||
args = append(args, "-enable-kvm")
|
||||
}
|
||||
|
||||
// machine, CPU, RAM
|
||||
if config.Machine != "" {
|
||||
args = append(args, "-machine", config.Machine)
|
||||
}
|
||||
|
||||
if config.CPU != "" {
|
||||
args = append(args, "-cpu", config.CPU)
|
||||
}
|
||||
|
||||
args = append(args, "-smp", fmt.Sprintf("%d,maxcpus=%d", config.SmpCount, config.MaxCpus))
|
||||
|
||||
args = append(args, "-m", fmt.Sprintf("%s,slots=%d,maxmem=%s",
|
||||
config.MemoryConfig.Size,
|
||||
config.MemoryConfig.Slots,
|
||||
config.MemoryConfig.Max))
|
||||
|
||||
// OVMF
|
||||
args = append(args, "-drive",
|
||||
fmt.Sprintf("if=%s,format=%s,unit=%d,file=%s,readonly=%s",
|
||||
config.OVMFCodeConfig.If,
|
||||
config.OVMFCodeConfig.Format,
|
||||
config.OVMFCodeConfig.Unit,
|
||||
config.OVMFCodeConfig.File,
|
||||
config.OVMFCodeConfig.ReadOnly))
|
||||
|
||||
args = append(args, "-drive",
|
||||
fmt.Sprintf("if=%s,format=%s,unit=%d,file=%s",
|
||||
config.OVMFVarsConfig.If,
|
||||
config.OVMFVarsConfig.Format,
|
||||
config.OVMFVarsConfig.Unit,
|
||||
config.OVMFVarsConfig.File))
|
||||
|
||||
// disk
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("virtio-scsi-pci,id=%s,disable-legacy=%s,iommu_platform=%t",
|
||||
config.VirtioScsiPciConfig.ID,
|
||||
config.VirtioScsiPciConfig.DisableLegacy,
|
||||
config.VirtioScsiPciConfig.IOMMUPlatform))
|
||||
|
||||
args = append(args, "-drive",
|
||||
fmt.Sprintf("file=%s,if=%s,id=%s,format=%s",
|
||||
config.DiskImgConfig.File,
|
||||
config.DiskImgConfig.If,
|
||||
config.DiskImgConfig.ID,
|
||||
config.DiskImgConfig.Format))
|
||||
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("scsi-hd,drive=%s", config.DiskImgConfig.ID))
|
||||
|
||||
// network
|
||||
args = append(args, "-netdev",
|
||||
fmt.Sprintf("user,id=%s,hostfwd=tcp::%d-:%d,hostfwd=tcp::%d-:%d,hostfwd=tcp::%d-:%d",
|
||||
config.NetDevConfig.ID,
|
||||
config.NetDevConfig.HostFwd1, config.NetDevConfig.GuestFwd1,
|
||||
config.NetDevConfig.HostFwd2, config.NetDevConfig.GuestFwd2,
|
||||
config.NetDevConfig.HostFwd3, config.NetDevConfig.GuestFwd3))
|
||||
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("virtio-net-pci,disable-legacy=%s,iommu_platform=%v,netdev=%s,romfile=%s",
|
||||
config.VirtioNetPciConfig.DisableLegacy,
|
||||
config.VirtioNetPciConfig.IOMMUPlatform,
|
||||
config.NetDevConfig.ID,
|
||||
config.VirtioNetPciConfig.ROMFile))
|
||||
|
||||
// SEV
|
||||
if config.EnableSEV {
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("sev-guest,id=%s,cbitpos=%d,reduced-phys-bits=%d",
|
||||
config.SevConfig.ID,
|
||||
config.SevConfig.CBitPos,
|
||||
config.SevConfig.ReducedPhysBits))
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("memory-encryption=%s", config.SevConfig.ID))
|
||||
}
|
||||
|
||||
// display
|
||||
if config.NoGraphic {
|
||||
args = append(args, "-nographic")
|
||||
}
|
||||
|
||||
args = append(args, "-monitor", config.Monitor)
|
||||
|
||||
return args
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/ultravioletrs/manager/internal"
|
||||
)
|
||||
|
||||
const qemuRelPath = "qemu-system-x86_64"
|
||||
const firmwareVars = "OVMF_VARS"
|
||||
const qcow2Img = "focal-server-cloudimg-amd64"
|
||||
|
||||
func CreateVM(ctx context.Context, cfg Config) (*exec.Cmd, error) {
|
||||
// create unique emu device identifiers
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return &exec.Cmd{}, err
|
||||
}
|
||||
qemuCfg := cfg
|
||||
qemuCfg.NetDevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.NetDevConfig.ID, id)
|
||||
qemuCfg.DiskImgConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.DiskImgConfig.ID, id)
|
||||
qemuCfg.VirtioScsiPciConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.VirtioScsiPciConfig.ID, id)
|
||||
qemuCfg.SevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.SevConfig.ID, id)
|
||||
|
||||
// copy firmware vars file
|
||||
srcFile := qemuCfg.OVMFVarsConfig.File
|
||||
dstFile := fmt.Sprintf("%s/%s-%s.fd", cfg.TmpFileLoc, firmwareVars, id)
|
||||
err = internal.CopyFile(srcFile, dstFile)
|
||||
if err != nil {
|
||||
return &exec.Cmd{}, err
|
||||
}
|
||||
qemuCfg.OVMFVarsConfig.File = dstFile
|
||||
|
||||
// copy qcow2 img file
|
||||
srcFile = qemuCfg.DiskImgConfig.File
|
||||
dstFile = fmt.Sprintf("%s/%s-%s.img", cfg.TmpFileLoc, qcow2Img, id)
|
||||
err = internal.CopyFile(srcFile, dstFile)
|
||||
if err != nil {
|
||||
return &exec.Cmd{}, err
|
||||
}
|
||||
qemuCfg.DiskImgConfig.File = dstFile
|
||||
|
||||
exe, args, err := ExecutableAndArgs(qemuCfg)
|
||||
if err != nil {
|
||||
return &exec.Cmd{}, err
|
||||
}
|
||||
cmd, err := runQemuVM(exe, args)
|
||||
if err != nil {
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func ExecutableAndArgs(cfg Config) (string, []string, error) {
|
||||
exe, err := exec.LookPath(qemuRelPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
args := constructQemuArgs(cfg)
|
||||
|
||||
if cfg.UseSudo {
|
||||
args = append([]string{exe}, args...)
|
||||
exe = "sudo"
|
||||
}
|
||||
|
||||
return exe, args, nil
|
||||
}
|
||||
|
||||
func runQemuVM(exe string, args []string) (*exec.Cmd, error) {
|
||||
cmd, err := internal.RunCmdStart(exe, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
backoff "github.com/cenkalti/backoff/v4"
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
"github.com/ultravioletrs/manager/manager/qemu"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMalformedEntity indicates malformed entity specification (e.g.
|
||||
// invalid username or password).
|
||||
ErrMalformedEntity = errors.New("malformed entity specification")
|
||||
|
||||
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
|
||||
// when accessing a protected resource.
|
||||
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
||||
|
||||
// ErrNotFound indicates a non-existent entity request.
|
||||
ErrNotFound = errors.New("entity not found")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fulfilled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
type Service interface {
|
||||
Run(ctx context.Context, computation []byte) (string, error)
|
||||
}
|
||||
|
||||
type managerService struct {
|
||||
agent agent.AgentServiceClient
|
||||
qemuCfg qemu.Config
|
||||
}
|
||||
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(agent agent.AgentServiceClient, qemuCfg qemu.Config) Service {
|
||||
return &managerService{
|
||||
agent: agent,
|
||||
qemuCfg: qemuCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *managerService) Run(ctx context.Context, computation []byte) (string, error) {
|
||||
_, err := qemu.CreateVM(ctx, ms.qemuCfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// different VM guests can't forward ports to the same ports on the same host
|
||||
ms.qemuCfg.HostFwd1++
|
||||
ms.qemuCfg.NetDevConfig.HostFwd2++
|
||||
ms.qemuCfg.NetDevConfig.HostFwd3++
|
||||
|
||||
var res *agent.RunResponse
|
||||
|
||||
err = backoff.Retry(func() error {
|
||||
res, err = ms.agent.Run(ctx, &agent.RunRequest{Computation: computation})
|
||||
return err
|
||||
}, backoff.NewExponentialBackOff())
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.Computation, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Package tracing provides tracing instrumentation for cocos auth service.
|
||||
//
|
||||
// This package provides tracing middleware for cocos auth service.
|
||||
// It can be used to trace incoming requests and add tracing capabilities to
|
||||
// cocos auth service.
|
||||
//
|
||||
// For more details about tracing instrumentation refer
|
||||
// to the documentation at https://docs.mainflux.io/tracing/.
|
||||
package tracing
|
||||
@@ -0,0 +1,27 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var _ manager.Service = (*tracingMiddleware)(nil)
|
||||
|
||||
type tracingMiddleware struct {
|
||||
tracer trace.Tracer
|
||||
svc manager.Service
|
||||
}
|
||||
|
||||
// New returns a new auth service with tracing capabilities.
|
||||
func New(svc manager.Service, tracer trace.Tracer) manager.Service {
|
||||
return &tracingMiddleware{tracer, svc}
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) Run(ctx context.Context, computation []byte) (string, error) {
|
||||
ctx, span := tm.tracer.Start(ctx, "run")
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.Run(ctx, computation)
|
||||
}
|
||||
@@ -15,6 +15,106 @@ var (
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ClientTLS bool `env:"CLIENT_TLS" envDefault:"false"`
|
||||
CACerts string `env:"CA_CERTS" envDefault:""`
|
||||
URL string `env:"URL" envDefault:"localhost:7001"`
|
||||
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Close closes gRPC connection.
|
||||
Close() error
|
||||
|
||||
// Secure is used for pretty printing TLS info.
|
||||
Secure() string
|
||||
|
||||
// Connection returns the gRPC connection.
|
||||
Connection() *gogrpc.ClientConn
|
||||
}
|
||||
|
||||
type client struct {
|
||||
*gogrpc.ClientConn
|
||||
cfg Config
|
||||
secure bool
|
||||
}
|
||||
|
||||
var _ Client = (*client)(nil)
|
||||
|
||||
func newClient(cfg Config) (Client, error) {
|
||||
conn, secure, err := connect(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client{
|
||||
ClientConn: conn,
|
||||
cfg: cfg,
|
||||
secure: secure,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) Close() error {
|
||||
if err := c.ClientConn.Close(); err != nil {
|
||||
return errors.Wrap(errGrpcClose, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) Secure() string {
|
||||
if c.secure {
|
||||
return "with TLS"
|
||||
}
|
||||
return "without TLS"
|
||||
}
|
||||
|
||||
func (c *client) Connection() *gogrpc.ClientConn {
|
||||
return c.ClientConn
|
||||
}
|
||||
|
||||
// connect creates new gRPC client and connect to gRPC server.
|
||||
func connect(cfg Config) (*gogrpc.ClientConn, bool, error) {
|
||||
var opts []gogrpc.DialOption
|
||||
secure := false
|
||||
tc := insecure.NewCredentials()
|
||||
|
||||
if cfg.ClientTLS && cfg.CACerts != "" {
|
||||
var err error
|
||||
tc, err = credentials.NewClientTLSFromFile(cfg.CACerts, "")
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
secure = true
|
||||
}
|
||||
|
||||
opts = append(opts, gogrpc.WithTransportCredentials(tc), gogrpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
|
||||
|
||||
conn, err := gogrpc.Dial(cfg.URL, opts...)
|
||||
if err != nil {
|
||||
return nil, secure, errors.Wrap(errGrpcConnect, err)
|
||||
}
|
||||
|
||||
return conn, secure, nil
|
||||
}
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
gogrpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var (
|
||||
errGrpcConnect = errors.New("failed to connect to grpc server")
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ClientTLS bool `env:"CLIENT_TLS" envDefault:"false"`
|
||||
CACerts string `env:"CA_CERTS" envDefault:""`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
managerapi "github.com/ultravioletrs/manager/manager/api/grpc"
|
||||
)
|
||||
|
||||
// NewClient creates new manager gRPC client instance.
|
||||
func NewClient(cfg Config) (Client, manager.ManagerServiceClient, error) {
|
||||
client, err := newClient(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return client, managerapi.NewClient(client.Connection(), cfg.Timeout), nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
|
||||
|
||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||
|
||||
[Exponential backoff][exponential backoff wiki]
|
||||
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
|
||||
in order to gradually find an acceptable rate.
|
||||
The retries exponentially increase and stop increasing when a certain threshold is met.
|
||||
|
||||
## Usage
|
||||
|
||||
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
|
||||
|
||||
Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
* I would like to keep this library as small as possible.
|
||||
* Please don't send a PR without opening an issue and discussing it first.
|
||||
* If proposed change is not a common use case, I will probably not accept it.
|
||||
|
||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||
[travis]: https://travis-ci.org/cenkalti/backoff
|
||||
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
|
||||
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||
|
||||
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
|
||||
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||
|
||||
[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Package backoff implements backoff algorithms for retrying operations.
|
||||
//
|
||||
// Use Retry function for retrying operations that may fail.
|
||||
// If Retry does not meet your needs,
|
||||
// copy/paste the function into your project and modify as you wish.
|
||||
//
|
||||
// There is also Ticker type similar to time.Ticker.
|
||||
// You can use it if you need to work with channels.
|
||||
//
|
||||
// See Examples section below for usage examples.
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// BackOff is a backoff policy for retrying an operation.
|
||||
type BackOff interface {
|
||||
// NextBackOff returns the duration to wait before retrying the operation,
|
||||
// or backoff. Stop to indicate that no more retries should be made.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// duration := backoff.NextBackOff();
|
||||
// if (duration == backoff.Stop) {
|
||||
// // Do not retry operation.
|
||||
// } else {
|
||||
// // Sleep for duration and retry operation.
|
||||
// }
|
||||
//
|
||||
NextBackOff() time.Duration
|
||||
|
||||
// Reset to initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Stop indicates that no more retries should be made for use in NextBackOff().
|
||||
const Stop time.Duration = -1
|
||||
|
||||
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
|
||||
// meaning that the operation is retried immediately without waiting, indefinitely.
|
||||
type ZeroBackOff struct{}
|
||||
|
||||
func (b *ZeroBackOff) Reset() {}
|
||||
|
||||
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
|
||||
|
||||
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
|
||||
// NextBackOff(), meaning that the operation should never be retried.
|
||||
type StopBackOff struct{}
|
||||
|
||||
func (b *StopBackOff) Reset() {}
|
||||
|
||||
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
|
||||
|
||||
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
|
||||
// This is in contrast to an exponential backoff policy,
|
||||
// which returns a delay that grows longer as you call NextBackOff() over and over again.
|
||||
type ConstantBackOff struct {
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
func (b *ConstantBackOff) Reset() {}
|
||||
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
|
||||
|
||||
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
|
||||
return &ConstantBackOff{Interval: d}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackOffContext is a backoff policy that stops retrying after the context
|
||||
// is canceled.
|
||||
type BackOffContext interface { // nolint: golint
|
||||
BackOff
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
type backOffContext struct {
|
||||
BackOff
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// WithContext returns a BackOffContext with context ctx
|
||||
//
|
||||
// ctx must not be nil
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
if b, ok := b.(*backOffContext); ok {
|
||||
return &backOffContext{
|
||||
BackOff: b.BackOff,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
return &backOffContext{
|
||||
BackOff: b,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func getContext(b BackOff) context.Context {
|
||||
if cb, ok := b.(BackOffContext); ok {
|
||||
return cb.Context()
|
||||
}
|
||||
if tb, ok := b.(*backOffTries); ok {
|
||||
return getContext(tb.delegate)
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (b *backOffContext) Context() context.Context {
|
||||
return b.ctx
|
||||
}
|
||||
|
||||
func (b *backOffContext) NextBackOff() time.Duration {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return Stop
|
||||
default:
|
||||
return b.BackOff.NextBackOff()
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized interval =
|
||||
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval.
|
||||
|
||||
For example, given the following parameters:
|
||||
|
||||
RetryInterval = 2
|
||||
RandomizationFactor = 0.5
|
||||
Multiplier = 2
|
||||
|
||||
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||
|
||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||
|
||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff returns Stop.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Stop time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// Clock is an interface that returns current time for BackOff.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
DefaultMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff() *ExponentialBackOff {
|
||||
b := &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Stop: Stop,
|
||||
Clock: SystemClock,
|
||||
}
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
type systemClock struct{}
|
||||
|
||||
func (t systemClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// SystemClock implements Clock interface that uses time.Now().
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
// Reset must be called before using b.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
elapsed := b.GetElapsedTime()
|
||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
b.incrementCurrentInterval()
|
||||
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
|
||||
return b.Stop
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
// is created and is reset when Reset() is called.
|
||||
//
|
||||
// The elapsed time is computed using time.Now().UnixNano(). It is
|
||||
// safe to call even while the backoff policy is used by a running
|
||||
// ticker.
|
||||
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||
return b.Clock.Now().Sub(b.startTime)
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
if randomizationFactor == 0 {
|
||||
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
|
||||
}
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type OperationWithData[T any] func() (T, error)
|
||||
|
||||
// An Operation is executing by Retry() or RetryNotify().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type Operation func() error
|
||||
|
||||
func (o Operation) withEmptyData() OperationWithData[struct{}] {
|
||||
return func() (struct{}, error) {
|
||||
return struct{}{}, o()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify is a notify-on-error function. It receives an operation error and
|
||||
// backoff delay if the operation failed (with an error).
|
||||
//
|
||||
// NOTE that if the backoff policy stated to stop retrying,
|
||||
// the notify function isn't called.
|
||||
type Notify func(error, time.Duration)
|
||||
|
||||
// Retry the operation o until it does not return error or BackOff stops.
|
||||
// o is guaranteed to be run at least once.
|
||||
//
|
||||
// If o returns a *PermanentError, the operation is not retried, and the
|
||||
// wrapped error is returned.
|
||||
//
|
||||
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||
// failed operation returns.
|
||||
func Retry(o Operation, b BackOff) error {
|
||||
return RetryNotify(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryWithData is like Retry but returns data in the response too.
|
||||
func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
|
||||
return RetryNotifyWithData(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration
|
||||
// for each failed attempt before sleep.
|
||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithData is like RetryNotify but returns data in the response too.
|
||||
func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
|
||||
return doRetryNotify(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||
// for each failed attempt before sleep.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||
_, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
|
||||
return err
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
|
||||
func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||
return doRetryNotify(operation, b, notify, t)
|
||||
}
|
||||
|
||||
func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||
var (
|
||||
err error
|
||||
next time.Duration
|
||||
res T
|
||||
)
|
||||
if t == nil {
|
||||
t = &defaultTimer{}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
ctx := getContext(b)
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
res, err = operation()
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var permanent *PermanentError
|
||||
if errors.As(err, &permanent) {
|
||||
return res, permanent.Err
|
||||
}
|
||||
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
if cerr := ctx.Err(); cerr != nil {
|
||||
return res, cerr
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
if notify != nil {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
t.Start(next)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return res, ctx.Err()
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PermanentError signals that the operation should not be retried.
|
||||
type PermanentError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *PermanentError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *PermanentError) Is(target error) bool {
|
||||
_, ok := target.(*PermanentError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &PermanentError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
|
||||
//
|
||||
// Ticks will continue to arrive when the previous operation is still running,
|
||||
// so operations that take a while to fail could run in quick succession.
|
||||
type Ticker struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
b BackOff
|
||||
ctx context.Context
|
||||
timer Timer
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewTicker returns a new Ticker containing a channel that will send
|
||||
// the time at times specified by the BackOff argument. Ticker is
|
||||
// guaranteed to tick at least once. The channel is closed when Stop
|
||||
// method is called or BackOff stops. It is not safe to manipulate the
|
||||
// provided backoff policy (notably calling NextBackOff or Reset)
|
||||
// while the ticker is running.
|
||||
func NewTicker(b BackOff) *Ticker {
|
||||
return NewTickerWithTimer(b, &defaultTimer{})
|
||||
}
|
||||
|
||||
// NewTickerWithTimer returns a new Ticker with a custom timer.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
|
||||
if timer == nil {
|
||||
timer = &defaultTimer{}
|
||||
}
|
||||
c := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: c,
|
||||
c: c,
|
||||
b: b,
|
||||
ctx: getContext(b),
|
||||
timer: timer,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
t.b.Reset()
|
||||
go t.run()
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||
func (t *Ticker) Stop() {
|
||||
t.stopOnce.Do(func() { close(t.stop) })
|
||||
}
|
||||
|
||||
func (t *Ticker) run() {
|
||||
c := t.c
|
||||
defer close(c)
|
||||
|
||||
// Ticker is guaranteed to tick at least once.
|
||||
afterC := t.send(time.Now())
|
||||
|
||||
for {
|
||||
if afterC == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case tick := <-afterC:
|
||||
afterC = t.send(tick)
|
||||
case <-t.stop:
|
||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||
return
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Ticker) send(tick time.Time) <-chan time.Time {
|
||||
select {
|
||||
case t.c <- tick:
|
||||
case <-t.stop:
|
||||
return nil
|
||||
}
|
||||
|
||||
next := t.b.NextBackOff()
|
||||
if next == Stop {
|
||||
t.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
t.timer.Start(next)
|
||||
return t.timer.C()
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
type Timer interface {
|
||||
Start(duration time.Duration)
|
||||
Stop()
|
||||
C() <-chan time.Time
|
||||
}
|
||||
|
||||
// defaultTimer implements Timer interface using time.Timer
|
||||
type defaultTimer struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// C returns the timers channel which receives the current time when the timer fires.
|
||||
func (t *defaultTimer) C() <-chan time.Time {
|
||||
return t.timer.C
|
||||
}
|
||||
|
||||
// Start starts the timer to fire after the given duration
|
||||
func (t *defaultTimer) Start(duration time.Duration) {
|
||||
if t.timer == nil {
|
||||
t.timer = time.NewTimer(duration)
|
||||
} else {
|
||||
t.timer.Reset(duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop is called when the timer is not used anymore and resources may be freed.
|
||||
func (t *defaultTimer) Stop() {
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
WithMaxRetries creates a wrapper around another BackOff, which will
|
||||
return Stop if NextBackOff() has been called too many times since
|
||||
the last time Reset() was called
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
func WithMaxRetries(b BackOff, max uint64) BackOff {
|
||||
return &backOffTries{delegate: b, maxTries: max}
|
||||
}
|
||||
|
||||
type backOffTries struct {
|
||||
delegate BackOff
|
||||
maxTries uint64
|
||||
numTries uint64
|
||||
}
|
||||
|
||||
func (b *backOffTries) NextBackOff() time.Duration {
|
||||
if b.maxTries == 0 {
|
||||
return Stop
|
||||
}
|
||||
if b.maxTries > 0 {
|
||||
if b.maxTries <= b.numTries {
|
||||
return Stop
|
||||
}
|
||||
b.numTries++
|
||||
}
|
||||
return b.delegate.NextBackOff()
|
||||
}
|
||||
|
||||
func (b *backOffTries) Reset() {
|
||||
b.numTries = 0
|
||||
b.delegate.Reset()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.idea/
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
Maintainer
|
||||
----------
|
||||
DigitalOcean, Inc
|
||||
|
||||
Original Authors
|
||||
----------------
|
||||
Ben LeMasurier <blemasurier@digitalocean.com>
|
||||
Matt Layher <mlayher@digitalocean.com>
|
||||
|
||||
Contributors
|
||||
------------
|
||||
Justin Kim <justin@digitalocean.com>
|
||||
Ricky Medina <rm@do.co>
|
||||
Charlie Drage <charlie@charliedrage.com>
|
||||
Michael Koppmann <me@mkoppmann.at>
|
||||
Simarpreet Singh <simar@linux.com>
|
||||
Alexander Polyakov <apolyakov@beget.com>
|
||||
Amanda Andrade <amanda.andrade@serpro.gov.br>
|
||||
Geoff Hickey <ghickey@digitalocean.com>
|
||||
Yuriy Taraday <yorik.sar@gmail.com>
|
||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
David Schneider <dsbrng25b@gmail.com>
|
||||
Alec Hothan <ahothan@gmail.com>
|
||||
Akos Varga <vrgakos@gmail.com>
|
||||
Peter Kurfer <peter.kurfer@gmail.com>
|
||||
Sam Roberts <sroberts@digitalocean.com>
|
||||
Moritz Wanzenböck <moritz.wanzenboeck@linbit.com>
|
||||
Jenni Griesmann <jgriesmann@digitalocean.com>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
The `go-libvirt` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/)
|
||||
for contributions.
|
||||
|
||||
If you'd like to contribute to the project, please
|
||||
[open an issue](https://github.com/digitalocean/go-libvirt/issues/new) or find an
|
||||
[existing issue](https://github.com/digitalocean/go-libvirt/issues) that you'd like
|
||||
to take on. This ensures that efforts are not duplicated, and that a new feature
|
||||
aligns with the focus of the rest of the repository.
|
||||
|
||||
Once your suggestion has been submitted and discussed, please be sure that your
|
||||
code meets the following criteria:
|
||||
- code is completely `gofmt`'d
|
||||
- new features or codepaths have appropriate test coverage
|
||||
- `go test ./...` passes
|
||||
- `go vet ./...` passes
|
||||
- `golint ./...` returns no warnings, including documentation comment warnings
|
||||
|
||||
In addition, if this is your first time contributing to the `go-libvirt` project,
|
||||
add your name and email address to the
|
||||
[AUTHORS](https://github.com/digitalocean/go-libvirt/blob/master/AUTHORS) file
|
||||
under the "Contributors" section using the format:
|
||||
`First Last <email@example.com>`.
|
||||
|
||||
Finally, submit a pull request for review!
|
||||
|
||||
Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/)
|
||||
if you'd like to discuss the project.
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
libvirt
|
||||
[](http://godoc.org/github.com/digitalocean/go-libvirt)
|
||||
[](https://github.com/digitalocean/go-libvirt/actions/)
|
||||
[](https://goreportcard.com/report/github.com/digitalocean/go-libvirt)
|
||||
====
|
||||
|
||||
Package `go-libvirt` provides a pure Go interface for interacting with libvirt.
|
||||
|
||||
Rather than using libvirt's C bindings, this package makes use of
|
||||
libvirt's RPC interface, as documented [here](https://libvirt.org/kbase/internals/rpc.html).
|
||||
Connections to the libvirt server may be local, or remote. RPC packets are encoded
|
||||
using the XDR standard as defined by [RFC 4506](https://tools.ietf.org/html/rfc4506.html).
|
||||
|
||||
libvirt's RPC interface is quite extensive, and changes from one version to the
|
||||
next, so this project uses a pair of code generators to build the go bindings.
|
||||
The code generators should be run whenever you want to build go-libvirt for a
|
||||
new version of libvirt. See the next section for directions on re-generating
|
||||
go-libvirt.
|
||||
|
||||
[Pull requests are welcome](https://github.com/digitalocean/go-libvirt/blob/master/CONTRIBUTING.md)!
|
||||
|
||||
Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/)
|
||||
if you'd like to discuss the project.
|
||||
|
||||
Running the Code Generators
|
||||
---------------------------
|
||||
|
||||
The code generator doesn't run automatically when you build go-libvirt. It's
|
||||
meant to be run manually any time you change the version of libvirt you're
|
||||
using. When you download go-libvirt it will come with generated files
|
||||
corresponding to a particular version of libvirt. You can use the library as-is,
|
||||
but the generated code may be missing libvirt functions, if you're using a newer
|
||||
version of libvirt, or it may have extra functions that will return
|
||||
'unimplemented' errors if you try to call them. If this is a problem, you should
|
||||
re-run the code generator. To do this, follow these steps:
|
||||
|
||||
- First, download a copy of the libvirt sources corresponding to the version you
|
||||
want to use.
|
||||
- Change directories into where you've unpacked your distribution of libvirt.
|
||||
- The second step depends on the version of libvirt you'd like to build against.
|
||||
It's not necessary to actually build libvirt, but it is necessary to run libvirt's
|
||||
"configure" step because it generates required files.
|
||||
- For libvirt < v6.7.0:
|
||||
- `$ mkdir build; cd build`
|
||||
- `$ ../autogen.sh`
|
||||
- For libvirt >= v6.7.0:
|
||||
- `$ meson setup build`
|
||||
- Finally, set the environment variable `LIBVIRT_SOURCE` to the directory you
|
||||
put libvirt into, and run `go generate ./...` from the go-libvirt directory.
|
||||
This runs both of the go-libvirt's code generators.
|
||||
|
||||
How to Use This Library
|
||||
-----------------------
|
||||
|
||||
Once you've vendored go-libvirt into your project, you'll probably want to call
|
||||
some libvirt functions. There's some example code below showing how to connect
|
||||
to libvirt and make one such call, but once you get past the introduction you'll
|
||||
next want to call some other libvirt functions. How do you find them?
|
||||
|
||||
Start with the [libvirt API reference](https://libvirt.org/html/index.html).
|
||||
Let's say you want to gracefully shutdown a VM, and after reading through the
|
||||
libvirt docs you determine that virDomainShutdown() is the function you want to
|
||||
call to do that. Where's that function in go-libvirt? We transform the names
|
||||
slightly when building the go bindings. There's no need for a global prefix like
|
||||
"vir" in Go, since all our functions are inside the package namespace, so we
|
||||
drop it. That means the Go function for `virDomainShutdown()` is just `DomainShutdown()`,
|
||||
and sure enough, you can find the Go function `DomainShutdown()` in libvirt.gen.go,
|
||||
with parameters and return values equivalent to those documented in the API
|
||||
reference.
|
||||
|
||||
Suppose you then decide you need more control over your shutdown, so you switch
|
||||
over to `virDomainShutdownFlags()`. As its name suggests, this function takes a
|
||||
flag parameter which has possible values specified in an enum called
|
||||
`virDomainShutdownFlagValues`. Flag types like this are a little tricky for the
|
||||
code generator, because the C functions just take an integer type - only the
|
||||
libvirt documentation actually ties the flags to the enum types. In most cases
|
||||
though we're able to generate a wrapper function with a distinct flag type,
|
||||
making it easier for Go tooling to suggest possible flag values while you're
|
||||
working. Checking the documentation for this function:
|
||||
|
||||
`godoc github.com/digitalocean/go-libvirt DomainShutdownFlags`
|
||||
|
||||
returns this:
|
||||
|
||||
`func (l *Libvirt) DomainShutdownFlags(Dom Domain, Flags DomainShutdownFlagValues) (err error)`
|
||||
|
||||
If you want to see the possible flag values, `godoc` can help again:
|
||||
|
||||
```
|
||||
$ godoc github.com/digitalocean/go-libvirt DomainShutdownFlagValues
|
||||
|
||||
type DomainShutdownFlagValues int32
|
||||
DomainShutdownFlagValues as declared in libvirt/libvirt-domain.h:1121
|
||||
|
||||
const (
|
||||
DomainShutdownDefault DomainShutdownFlagValues = iota
|
||||
DomainShutdownAcpiPowerBtn DomainShutdownFlagValues = 1
|
||||
DomainShutdownGuestAgent DomainShutdownFlagValues = 2
|
||||
DomainShutdownInitctl DomainShutdownFlagValues = 4
|
||||
DomainShutdownSignal DomainShutdownFlagValues = 8
|
||||
DomainShutdownParavirt DomainShutdownFlagValues = 16
|
||||
)
|
||||
DomainShutdownFlagValues enumeration from libvirt/libvirt-domain.h:1121
|
||||
```
|
||||
|
||||
One other suggestion: most of the code in go-libvirt is now generated, but a few
|
||||
hand-written routines still exist in libvirt.go, and wrap calls to the generated
|
||||
code with slightly different parameters or return values. We suggest avoiding
|
||||
these hand-written routines and calling the generated routines in libvirt.gen.go
|
||||
instead. Over time these handwritten routines will be removed from go-libvirt.
|
||||
|
||||
Warning
|
||||
-------
|
||||
|
||||
While these package are reasonably well-tested and have seen some use inside of
|
||||
DigitalOcean, there may be subtle bugs which could cause the packages to act
|
||||
in unexpected ways. Use at your own risk!
|
||||
|
||||
In addition, the API is not considered stable at this time. If you would like
|
||||
to include package `libvirt` in a project, we highly recommend vendoring it into
|
||||
your project.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This dials libvirt on the local machine, but you can substitute the first
|
||||
// two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
|
||||
// a remote machine.
|
||||
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to dial libvirt: %v", err)
|
||||
}
|
||||
|
||||
l := libvirt.New(c)
|
||||
if err := l.Connect(); err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
v, err := l.Version()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve libvirt version: %v", err)
|
||||
}
|
||||
fmt.Println("Version:", v)
|
||||
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve domains: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("ID\tName\t\tUUID")
|
||||
fmt.Printf("--------------------------------------------------------\n")
|
||||
for _, d := range domains {
|
||||
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
|
||||
}
|
||||
|
||||
if err := l.Disconnect(); err != nil {
|
||||
log.Fatalf("failed to disconnect: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Version: 1.3.4
|
||||
ID Name UUID
|
||||
--------------------------------------------------------
|
||||
1 Test-1 dc329f87d4de47198cfd2e21c6105b01
|
||||
2 Test-2 dc229f87d4de47198cfd2e21c6105b01
|
||||
```
|
||||
|
||||
Example (Connect to libvirt via TLS over TCP)
|
||||
-------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This dials libvirt on the local machine
|
||||
// It connects to libvirt via TLS over TCP
|
||||
// To connect to a remote machine, you need to have the ca/cert/key of it.
|
||||
keyFileXML, err := ioutil.ReadFile("/etc/pki/libvirt/private/clientkey.pem")
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
certFileXML, err := ioutil.ReadFile("/etc/pki/libvirt/clientcert.pem")
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
caFileXML, err := ioutil.ReadFile("/etc/pki/CA/cacert.pem")
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
cert, err := tls.X509KeyPair([]byte(certFileXML), []byte(keyFileXML))
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
roots.AppendCertsFromPEM([]byte(caFileXML))
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: roots,
|
||||
}
|
||||
|
||||
// Use host name or IP which is valid in certificate
|
||||
addr := "10.10.10.10"
|
||||
port := "16514"
|
||||
c, err := tls.Dial("tcp", addr + ":" + port, config)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to dial libvirt: %v", err)
|
||||
}
|
||||
|
||||
// Drop a byte before libvirt.New(c)
|
||||
// More details at https://github.com/digitalocean/go-libvirt/issues/89
|
||||
// Remove this line if the issue does not exist any more
|
||||
c.Read(make([]byte, 1))
|
||||
|
||||
l := libvirt.New(c)
|
||||
if err := l.Connect(); err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
v, err := l.Version()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve libvirt version: %v", err)
|
||||
}
|
||||
fmt.Println("Version:", v)
|
||||
|
||||
// Return both running and stopped VMs
|
||||
flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive
|
||||
domains, _, err := l.ConnectListAllDomains(1, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve domains: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("ID\tName\t\tUUID")
|
||||
fmt.Println("--------------------------------------------------------")
|
||||
for _, d := range domains {
|
||||
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
|
||||
}
|
||||
|
||||
if err := l.Disconnect(); err != nil {
|
||||
log.Fatalf("failed to disconnect: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Running the Integration Tests
|
||||
-----------------------------
|
||||
|
||||
GitHub actions workflows are defined in [.github/workflows](.github/workflows)
|
||||
and can be triggered manually in the GitHub UI after pushing a branch. There
|
||||
are not currently convenient scripts for setting up and running integration tests
|
||||
locally, but installing libvirt and defining only the artifacts described by the
|
||||
files in testdata should be sufficient to be able to run the integration test file
|
||||
against.
|
||||
+2500
File diff suppressed because it is too large
Load Diff
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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 libvirt is a pure Go interface to libvirt.
|
||||
//
|
||||
// Rather than using Libvirt's C bindings, this package makes use of Libvirt's
|
||||
// RPC interface, as documented here: https://libvirt.org/internals/rpc.html.
|
||||
// Connections to the libvirt server may be local, or remote. RPC packets are
|
||||
// encoded using the XDR standard as defined by RFC 4506.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net"
|
||||
// "time"
|
||||
//
|
||||
// "github.com/digitalocean/go-libvirt"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// // This dials libvirt on the local machine, but you can substitute the first
|
||||
// // two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
|
||||
// // a remote machine.
|
||||
// c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
// if err != nil {
|
||||
// log.Fatalf("failed to dial libvirt: %v", err)
|
||||
// }
|
||||
//
|
||||
// l := libvirt.New(c)
|
||||
// if err := l.Connect(); err != nil {
|
||||
// log.Fatalf("failed to connect: %v", err)
|
||||
// }
|
||||
//
|
||||
// v, err := l.Version()
|
||||
// if err != nil {
|
||||
// log.Fatalf("failed to retrieve libvirt version: %v", err)
|
||||
// }
|
||||
// fmt.Println("Version:", v)
|
||||
//
|
||||
// domains, err := l.Domains()
|
||||
// if err != nil {
|
||||
// log.Fatalf("failed to retrieve domains: %v", err)
|
||||
// }
|
||||
//
|
||||
// fmt.Println("ID\tName\t\tUUID")
|
||||
// fmt.Printf("--------------------------------------------------------\n")
|
||||
// for _, d := range domains {
|
||||
// fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
|
||||
// }
|
||||
//
|
||||
// if err := l.Disconnect(); err != nil {
|
||||
// log.Fatalf("failed to disconnect: %v", err)
|
||||
// }
|
||||
// }
|
||||
package libvirt
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
//
|
||||
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
|
||||
//
|
||||
// To regenerate, run 'go generate' in internal/lvgen.
|
||||
//
|
||||
|
||||
package constants
|
||||
|
||||
// These are libvirt procedure numbers which correspond to each respective
|
||||
// API call between remote_internal driver and libvirtd. Each procedure is
|
||||
// identified by a unique number.
|
||||
const (
|
||||
// From enums:
|
||||
// QEMUProcDomainMonitorCommand is libvirt's QEMU_PROC_DOMAIN_MONITOR_COMMAND
|
||||
QEMUProcDomainMonitorCommand = 1
|
||||
// QEMUProcDomainAttach is libvirt's QEMU_PROC_DOMAIN_ATTACH
|
||||
QEMUProcDomainAttach = 2
|
||||
// QEMUProcDomainAgentCommand is libvirt's QEMU_PROC_DOMAIN_AGENT_COMMAND
|
||||
QEMUProcDomainAgentCommand = 3
|
||||
// QEMUProcConnectDomainMonitorEventRegister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER
|
||||
QEMUProcConnectDomainMonitorEventRegister = 4
|
||||
// QEMUProcConnectDomainMonitorEventDeregister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER
|
||||
QEMUProcConnectDomainMonitorEventDeregister = 5
|
||||
// QEMUProcDomainMonitorEvent is libvirt's QEMU_PROC_DOMAIN_MONITOR_EVENT
|
||||
QEMUProcDomainMonitorEvent = 6
|
||||
|
||||
|
||||
// From consts:
|
||||
// QEMUProgram is libvirt's QEMU_PROGRAM
|
||||
QEMUProgram = 0x20008087
|
||||
// QEMUProtocolVersion is libvirt's QEMU_PROTOCOL_VERSION
|
||||
QEMUProtocolVersion = 1
|
||||
)
|
||||
Generated
Vendored
+1023
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2020 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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 event
|
||||
|
||||
// Event represents an internal Event.
|
||||
type Event interface {
|
||||
GetCallbackID() int32
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright 2020 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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 event
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// emptyEvent is used as a zero-value. Clients will never receive one of these;
|
||||
// they are only here to satisfy the compiler. See the comments in process() for
|
||||
// more information.
|
||||
type emptyEvent struct{}
|
||||
|
||||
func (emptyEvent) GetCallbackID() int32 { return 0 }
|
||||
|
||||
// Stream is an unbounded buffered event channel. The implementation
|
||||
// consists of a pair of unbuffered channels and a goroutine to manage them.
|
||||
// Client behavior will not cause incoming events to block.
|
||||
type Stream struct {
|
||||
// Program specifies the source of the events - libvirt or QEMU.
|
||||
Program uint32
|
||||
|
||||
// CallbackID is returned by the event registration call.
|
||||
CallbackID int32
|
||||
|
||||
// manage unbounded channel behavior.
|
||||
queue []Event
|
||||
qlen chan (chan int)
|
||||
in, out chan Event
|
||||
|
||||
// terminates processing
|
||||
shutdown context.CancelFunc
|
||||
}
|
||||
|
||||
// NewStream configures a new Event Stream. Incoming events are appended to a
|
||||
// queue, which is then relayed to the listening client. Client behavior will
|
||||
// not cause incoming events to block. It is the responsibility of the caller
|
||||
// to terminate the Stream via Shutdown() when no longer in use.
|
||||
func NewStream(program uint32, cbID int32) *Stream {
|
||||
s := &Stream{
|
||||
Program: program,
|
||||
CallbackID: cbID,
|
||||
in: make(chan Event),
|
||||
out: make(chan Event),
|
||||
qlen: make(chan (chan int)),
|
||||
}
|
||||
|
||||
// Start the processing loop, which will return a routine we can use to
|
||||
// shut the queue down later.
|
||||
s.shutdown = s.start()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Len will return the current count of events in the internal queue for a
|
||||
// stream. It does this by sending a message to the stream's process() loop,
|
||||
// which will then write the current length to the channel contained in that
|
||||
// message.
|
||||
func (s *Stream) Len() int {
|
||||
// Send a request to the process() loop to get the current length of the
|
||||
// queue
|
||||
ch := make(chan int)
|
||||
s.qlen <- ch
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// Recv returns the next available event from the Stream's queue.
|
||||
func (s *Stream) Recv() chan Event {
|
||||
return s.out
|
||||
}
|
||||
|
||||
// Push appends a new event to the queue.
|
||||
func (s *Stream) Push(e Event) {
|
||||
s.in <- e
|
||||
}
|
||||
|
||||
// Shutdown gracefully terminates Stream processing, releasing all internal
|
||||
// resources. Events which have not yet been received by the client will be
|
||||
// dropped. Subsequent calls to Shutdown() are idempotent.
|
||||
func (s *Stream) Shutdown() {
|
||||
if s.shutdown != nil {
|
||||
s.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// start starts the event processing loop, which will continue to run until
|
||||
// terminated by the returned context.CancelFunc.
|
||||
func (s *Stream) start() context.CancelFunc {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go s.process(ctx)
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
// process manages an Stream's lifecycle until canceled by the provided context.
|
||||
// Incoming events are appended to a queue which is then relayed to the
|
||||
// listening client. New events pushed onto the queue will not block if the
|
||||
// client is not actively polling for them; the stream will buffer them
|
||||
// internally.
|
||||
func (s *Stream) process(ctx context.Context) {
|
||||
// Close the output channel so that clients know this stream is finished.
|
||||
// We don't close s.in to avoid creating a race with the stream's Push()
|
||||
// function.
|
||||
defer close(s.out)
|
||||
|
||||
// This function is used to retrieve the next event from the queue, to be
|
||||
// sent to the client. If there are no more events to send, it returns a nil
|
||||
// channel and a zero-value event.
|
||||
nextEvent := func() (chan Event, Event) {
|
||||
sendCh := chan Event(nil)
|
||||
next := Event(emptyEvent{})
|
||||
if len(s.queue) > 0 {
|
||||
sendCh = s.out
|
||||
next = s.queue[0]
|
||||
}
|
||||
return sendCh, next
|
||||
}
|
||||
|
||||
// The select statement in this loop relies on the fact that a send to a nil
|
||||
// channel will block forever. If we have no entries in the queue, the
|
||||
// sendCh variable will be nil, so the clause that attempts to send an event
|
||||
// to the client will never complete. Clients will never receive an
|
||||
// emptyEvent.
|
||||
for {
|
||||
sendCh, nextEvt := nextEvent()
|
||||
|
||||
select {
|
||||
// new event received, append to queue
|
||||
case e := <-s.in:
|
||||
s.queue = append(s.queue, e)
|
||||
|
||||
case lenCh := <-s.qlen:
|
||||
lenCh <- len(s.queue)
|
||||
|
||||
// client received an event, pop from queue
|
||||
case sendCh <- nextEvt:
|
||||
s.queue = s.queue[1:]
|
||||
|
||||
// shutdown requested
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+896
@@ -0,0 +1,896 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errMaxSlice = "data exceeds max slice limit"
|
||||
errIODecode = "%s while decoding %d bytes"
|
||||
)
|
||||
|
||||
/*
|
||||
Unmarshal parses XDR-encoded data into the value pointed to by v reading from
|
||||
reader r and returning the total number of bytes read. An addressable pointer
|
||||
must be provided since Unmarshal needs to both store the result of the decode as
|
||||
well as obtain target type information. Unmarhsal traverses v recursively and
|
||||
automatically indirects pointers through arbitrary depth, allocating them as
|
||||
necessary, to decode the data into the underlying value pointed to.
|
||||
|
||||
Unmarshal uses reflection to determine the type of the concrete value contained
|
||||
by v and performs a mapping of underlying XDR types to Go types as follows:
|
||||
|
||||
Go Type <- XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int <- XDR Integer
|
||||
uint8, uint16, uint32, uint <- XDR Unsigned Integer
|
||||
int64 <- XDR Hyper Integer
|
||||
uint64 <- XDR Unsigned Hyper Integer
|
||||
bool <- XDR Boolean
|
||||
float32 <- XDR Floating-Point
|
||||
float64 <- XDR Double-Precision Floating-Point
|
||||
string <- XDR String
|
||||
byte <- XDR Integer
|
||||
[]byte <- XDR Variable-Length Opaque Data
|
||||
[#]byte <- XDR Fixed-Length Opaque Data
|
||||
[]<type> <- XDR Variable-Length Array
|
||||
[#]<type> <- XDR Fixed-Length Array
|
||||
struct <- XDR Structure
|
||||
map <- XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time <- XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic unmarshalling of variable and fixed-length arrays of uint8s
|
||||
requires a special struct tag `xdropaque:"false"` since byte slices
|
||||
and byte arrays are assumed to be opaque data and byte is a Go alias
|
||||
for uint8 thus indistinguishable under reflection
|
||||
* Cyclic data structures are not supported and will result in infinite
|
||||
loops
|
||||
|
||||
If any issues are encountered during the unmarshalling process, an
|
||||
UnmarshalError is returned with a human readable description as well as
|
||||
an ErrorCode value for further inspection from sophisticated callers. Some
|
||||
potential issues are unsupported Go types, attempting to decode a value which is
|
||||
too large to fit into a specified Go type, and exceeding max slice limitations.
|
||||
*/
|
||||
func Unmarshal(r io.Reader, v interface{}) (int, error) {
|
||||
d := Decoder{r: r}
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// UnmarshalLimited is identical to Unmarshal but it sets maxReadSize in order
|
||||
// to cap reads.
|
||||
func UnmarshalLimited(r io.Reader, v interface{}, maxSize uint) (int, error) {
|
||||
d := Decoder{r: r, maxReadSize: maxSize}
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// TypeDecoder lets a caller provide a custom decode routine for a custom type.
|
||||
type TypeDecoder interface {
|
||||
Decode(*Decoder, reflect.Value) (int, error)
|
||||
}
|
||||
|
||||
// A Decoder wraps an io.Reader that is expected to provide an XDR-encoded byte
|
||||
// stream and provides several exposed methods to manually decode various XDR
|
||||
// primitives without relying on reflection. The NewDecoder function can be
|
||||
// used to get a new Decoder directly.
|
||||
//
|
||||
// Typically, Unmarshal should be used instead of manual decoding. A Decoder
|
||||
// is exposed so it is possible to perform manual decoding should it be
|
||||
// necessary in complex scenarios where automatic reflection-based decoding
|
||||
// won't work.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
|
||||
// maxReadSize is the default maximum bytes an element can contain. 0
|
||||
// is unlimited and provides backwards compatability. Setting it to a
|
||||
// non-zero value caps reads.
|
||||
maxReadSize uint
|
||||
|
||||
// customTypes is a map allowing the caller to provide decoder routines for
|
||||
// custom types known only to itself.
|
||||
customTypes map[string]TypeDecoder
|
||||
}
|
||||
|
||||
// DecodeInt treats the next 4 bytes as an XDR encoded integer and returns the
|
||||
// result as an int32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.1 - Integer
|
||||
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
|
||||
func (d *Decoder) DecodeInt() (int32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeInt", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := int32(buf[3]) | int32(buf[2])<<8 |
|
||||
int32(buf[1])<<16 | int32(buf[0])<<24
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeUint treats the next 4 bytes as an XDR encoded unsigned integer and
|
||||
// returns the result as a uint32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.2 - Unsigned Integer
|
||||
// 32-bit big-endian unsigned integer in range [0, 4294967295]
|
||||
func (d *Decoder) DecodeUint() (uint32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeUint", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := uint32(buf[3]) | uint32(buf[2])<<8 |
|
||||
uint32(buf[1])<<16 | uint32(buf[0])<<24
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeEnum treats the next 4 bytes as an XDR encoded enumeration value and
|
||||
// returns the result as an int32 after verifying that the value is in the
|
||||
// provided map of valid values. It also returns the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the parsed enumeration value is not one of the provided valid values.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.3 - Enumeration
|
||||
// Represented as an XDR encoded signed integer
|
||||
func (d *Decoder) DecodeEnum(validEnums map[int32]bool) (int32, int, error) {
|
||||
val, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
if !validEnums[val] {
|
||||
err := unmarshalError("DecodeEnum", ErrBadEnumValue,
|
||||
"invalid enum", val, nil)
|
||||
return 0, n, err
|
||||
}
|
||||
return val, n, nil
|
||||
}
|
||||
|
||||
// DecodeBool treats the next 4 bytes as an XDR encoded boolean value and
|
||||
// returns the result as a bool along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the parsed value is not a 0 or 1.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.4 - Boolean
|
||||
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
|
||||
func (d *Decoder) DecodeBool() (bool, int, error) {
|
||||
val, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return false, n, err
|
||||
}
|
||||
switch val {
|
||||
case 0:
|
||||
return false, n, nil
|
||||
case 1:
|
||||
return true, n, nil
|
||||
}
|
||||
|
||||
err = unmarshalError("DecodeBool", ErrBadEnumValue, "bool not 0 or 1",
|
||||
val, nil)
|
||||
return false, n, err
|
||||
}
|
||||
|
||||
// DecodeHyper treats the next 8 bytes as an XDR encoded hyper value and
|
||||
// returns the result as an int64 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Hyper Integer
|
||||
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
|
||||
func (d *Decoder) DecodeHyper() (int64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeHyper", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := int64(buf[7]) | int64(buf[6])<<8 |
|
||||
int64(buf[5])<<16 | int64(buf[4])<<24 |
|
||||
int64(buf[3])<<32 | int64(buf[2])<<40 |
|
||||
int64(buf[1])<<48 | int64(buf[0])<<56
|
||||
return rv, n, err
|
||||
}
|
||||
|
||||
// DecodeUhyper treats the next 8 bytes as an XDR encoded unsigned hyper value
|
||||
// and returns the result as a uint64 along with the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Unsigned Hyper Integer
|
||||
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
|
||||
func (d *Decoder) DecodeUhyper() (uint64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeUhyper", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := uint64(buf[7]) | uint64(buf[6])<<8 |
|
||||
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
|
||||
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
|
||||
uint64(buf[1])<<48 | uint64(buf[0])<<56
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeFloat treats the next 4 bytes as an XDR encoded floating point and
|
||||
// returns the result as a float32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.6 - Floating Point
|
||||
// 32-bit single-precision IEEE 754 floating point
|
||||
func (d *Decoder) DecodeFloat() (float32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeFloat", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
val := uint32(buf[3]) | uint32(buf[2])<<8 |
|
||||
uint32(buf[1])<<16 | uint32(buf[0])<<24
|
||||
return math.Float32frombits(val), n, nil
|
||||
}
|
||||
|
||||
// DecodeDouble treats the next 8 bytes as an XDR encoded double-precision
|
||||
// floating point and returns the result as a float64 along with the number of
|
||||
// bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.7 - Double-Precision Floating Point
|
||||
// 64-bit double-precision IEEE 754 floating point
|
||||
func (d *Decoder) DecodeDouble() (float64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeDouble", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
val := uint64(buf[7]) | uint64(buf[6])<<8 |
|
||||
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
|
||||
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
|
||||
uint64(buf[1])<<48 | uint64(buf[0])<<56
|
||||
return math.Float64frombits(val), n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.8 - Quadruple-Precision Floating Point
|
||||
// 128-bit quadruple-precision floating point
|
||||
// Not Implemented
|
||||
|
||||
// DecodeFixedOpaque treats the next 'size' bytes as XDR encoded opaque data and
|
||||
// returns the result as a byte slice along with the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining to
|
||||
// satisfy the passed size, including the necessary padding to make it a
|
||||
// multiple of 4.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.9 - Fixed-Length Opaque Data
|
||||
// Fixed-length uninterpreted data zero-padded to a multiple of four
|
||||
func (d *Decoder) DecodeFixedOpaque(size int32) ([]byte, int, error) {
|
||||
// Nothing to do if size is 0.
|
||||
if size == 0 {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
pad := (4 - (size % 4)) % 4
|
||||
paddedSize := size + pad
|
||||
if uint(paddedSize) > uint(math.MaxInt32) {
|
||||
err := unmarshalError("DecodeFixedOpaque", ErrOverflow,
|
||||
errMaxSlice, paddedSize, nil)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
buf := make([]byte, paddedSize)
|
||||
n, err := io.ReadFull(d.r, buf)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), paddedSize)
|
||||
err := unmarshalError("DecodeFixedOpaque", ErrIO, msg, buf[:n],
|
||||
err)
|
||||
return nil, n, err
|
||||
}
|
||||
return buf[0:size], n, nil
|
||||
}
|
||||
|
||||
// DecodeOpaque treats the next bytes as variable length XDR encoded opaque
|
||||
// data and returns the result as a byte slice along with the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the opaque data is larger than the max length of a Go slice.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.10 - Variable-Length Opaque Data
|
||||
// Unsigned integer length followed by fixed opaque data of that length
|
||||
func (d *Decoder) DecodeOpaque() ([]byte, int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err := unmarshalError("DecodeOpaque", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return nil, n, err
|
||||
}
|
||||
|
||||
rv, n2, err := d.DecodeFixedOpaque(int32(dataLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeString treats the next bytes as a variable length XDR encoded string
|
||||
// and returns the result as a string along with the number of bytes actually
|
||||
// read. Character encoding is assumed to be UTF-8 and therefore ASCII
|
||||
// compatible. If the underlying character encoding is not compatibile with
|
||||
// this assumption, the data can instead be read as variable-length opaque data
|
||||
// (DecodeOpaque) and manually converted as needed.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the string data is larger than the max length of a Go slice.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.11 - String
|
||||
// Unsigned integer length followed by bytes zero-padded to a multiple of
|
||||
// four
|
||||
func (d *Decoder) DecodeString() (string, int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return "", n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err = unmarshalError("DecodeString", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return "", n, err
|
||||
}
|
||||
|
||||
opaque, n2, err := d.DecodeFixedOpaque(int32(dataLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return "", n, err
|
||||
}
|
||||
return string(opaque), n, nil
|
||||
}
|
||||
|
||||
// decodeFixedArray treats the next bytes as a series of XDR encoded elements
|
||||
// of the same type as the array represented by the reflection value and decodes
|
||||
// each element into the passed array. The ignoreOpaque flag controls whether
|
||||
// or not uint8 (byte) elements should be decoded individually or as a fixed
|
||||
// sequence of opaque data. It returns the the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.12 - Fixed-Length Array
|
||||
// Individually XDR encoded array elements
|
||||
func (d *Decoder) decodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
// Treat [#]byte (byte is alias for uint8) as opaque data unless
|
||||
// ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
data, n, err := d.DecodeFixedOpaque(int32(v.Len()))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
reflect.Copy(v, reflect.ValueOf(data))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode each array element.
|
||||
var n int
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
n2, err := d.decode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeArray treats the next bytes as a variable length series of XDR encoded
|
||||
// elements of the same type as the array represented by the reflection value.
|
||||
// The number of elements is obtained by first decoding the unsigned integer
|
||||
// element count. Then each element is decoded into the passed array. The
|
||||
// ignoreOpaque flag controls whether or not uint8 (byte) elements should be
|
||||
// decoded individually or as a variable sequence of opaque data. It returns
|
||||
// the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.13 - Variable-Length Array
|
||||
// Unsigned integer length followed by individually XDR encoded array
|
||||
// elements
|
||||
func (d *Decoder) decodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err := unmarshalError("decodeArray", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Allocate storage for the slice elements (the underlying array) if
|
||||
// existing slice does not have enough capacity.
|
||||
sliceLen := int(dataLen)
|
||||
if v.Cap() < sliceLen {
|
||||
v.Set(reflect.MakeSlice(v.Type(), sliceLen, sliceLen))
|
||||
}
|
||||
if v.Len() < sliceLen {
|
||||
v.SetLen(sliceLen)
|
||||
}
|
||||
|
||||
// Treat []byte (byte is alias for uint8) as opaque data unless ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
data, n2, err := d.DecodeFixedOpaque(int32(sliceLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
v.SetBytes(data)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode each slice element.
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
n2, err := d.decode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeStruct treats the next bytes as a series of XDR encoded elements
|
||||
// of the same type as the exported fields of the struct represented by the
|
||||
// passed reflection value. Pointers are automatically indirected and
|
||||
// allocated as necessary. It returns the the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.14 - Structure
|
||||
// XDR encoded elements in the order of their declaration in the struct
|
||||
func (d *Decoder) decodeStruct(v reflect.Value) (int, error) {
|
||||
var n int
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// Skip unexported fields.
|
||||
vtf := vt.Field(i)
|
||||
if vtf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Indirect through pointers allocating them as needed and
|
||||
// ensure the field is settable.
|
||||
vf := v.Field(i)
|
||||
vf, err := d.indirect(vf)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if !vf.CanSet() {
|
||||
msg := fmt.Sprintf("can't decode to unsettable '%v'",
|
||||
vf.Type().String())
|
||||
err := unmarshalError("decodeStruct", ErrNotSettable,
|
||||
msg, nil, nil)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Handle non-opaque data to []uint8 and [#]uint8 based on
|
||||
// struct tag.
|
||||
tag := vtf.Tag.Get("xdropaque")
|
||||
if tag == "false" {
|
||||
switch vf.Kind() {
|
||||
case reflect.Slice:
|
||||
n2, err := d.decodeArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
|
||||
case reflect.Array:
|
||||
n2, err := d.decodeFixedArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode each struct field.
|
||||
n2, err := d.decode(vf)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.15 - Discriminated Union
|
||||
// RFC Section 4.16 - Void
|
||||
// RFC Section 4.17 - Constant
|
||||
// RFC Section 4.18 - Typedef
|
||||
// RFC Section 4.19 - Optional data
|
||||
// RFC Sections 4.15 though 4.19 only apply to the data specification language
|
||||
// which is not implemented by this package. In the case of discriminated
|
||||
// unions, struct tags are used to perform a similar function.
|
||||
|
||||
// decodeMap treats the next bytes as an XDR encoded variable array of 2-element
|
||||
// structures whose fields are of the same type as the map keys and elements
|
||||
// represented by the passed reflection value. Pointers are automatically
|
||||
// indirected and allocated as necessary. It returns the the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the elements.
|
||||
func (d *Decoder) decodeMap(v reflect.Value) (int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Allocate storage for the underlying map if needed.
|
||||
vt := v.Type()
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.MakeMap(vt))
|
||||
}
|
||||
|
||||
// Decode each key and value according to their type.
|
||||
keyType := vt.Key()
|
||||
elemType := vt.Elem()
|
||||
for i := uint32(0); i < dataLen; i++ {
|
||||
key := reflect.New(keyType).Elem()
|
||||
n2, err := d.decode(key)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
val := reflect.New(elemType).Elem()
|
||||
n2, err = d.decode(val)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
v.SetMapIndex(key, val)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeInterface examines the interface represented by the passed reflection
|
||||
// value to detect whether it is an interface that can be decoded into and
|
||||
// if it is, extracts the underlying value to pass back into the decode function
|
||||
// for decoding according to its type. It returns the the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the interface.
|
||||
func (d *Decoder) decodeInterface(v reflect.Value) (int, error) {
|
||||
if v.IsNil() || !v.CanInterface() {
|
||||
msg := fmt.Sprintf("can't decode to nil interface")
|
||||
err := unmarshalError("decodeInterface", ErrNilInterface, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Extract underlying value from the interface and indirect through
|
||||
// pointers allocating them as needed.
|
||||
ve := reflect.ValueOf(v.Interface())
|
||||
ve, err := d.indirect(ve)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ve.CanSet() {
|
||||
msg := fmt.Sprintf("can't decode to unsettable '%v'",
|
||||
ve.Type().String())
|
||||
err := unmarshalError("decodeInterface", ErrNotSettable, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
return d.decode(ve)
|
||||
}
|
||||
|
||||
// decode is the main workhorse for unmarshalling via reflection. It uses
|
||||
// the passed reflection value to choose the XDR primitives to decode from
|
||||
// the encapsulated reader. It is a recursive function,
|
||||
// so cyclic data structures are not supported and will result in an infinite
|
||||
// loop. It returns the the number of bytes actually read.
|
||||
func (d *Decoder) decode(v reflect.Value) (int, error) {
|
||||
if !v.IsValid() {
|
||||
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
|
||||
err := unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Indirect through pointers allocating them as needed.
|
||||
ve, err := d.indirect(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Handle time.Time values by decoding them as an RFC3339 formatted
|
||||
// string with nanosecond precision. Check the type string rather
|
||||
// than doing a full blown conversion to interface and type assertion
|
||||
// since checking a string is much quicker.
|
||||
switch ve.Type().String() {
|
||||
case "time.Time":
|
||||
// Read the value as a string and parse it.
|
||||
timeString, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ttv, err := time.Parse(time.RFC3339, timeString)
|
||||
if err != nil {
|
||||
err := unmarshalError("decode", ErrParseTime,
|
||||
err.Error(), timeString, err)
|
||||
return n, err
|
||||
}
|
||||
ve.Set(reflect.ValueOf(ttv))
|
||||
return n, nil
|
||||
}
|
||||
// If this type is in our custom types map, call the decode routine set up
|
||||
// for it.
|
||||
if dt, ok := d.customTypes[ve.Type().String()]; ok {
|
||||
return dt.Decode(d, v)
|
||||
}
|
||||
|
||||
// Handle native Go types.
|
||||
switch ve.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
|
||||
i, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if ve.OverflowInt(int64(i)) {
|
||||
msg := fmt.Sprintf("signed integer too large to fit '%s'",
|
||||
ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrOverflow, msg, i, nil)
|
||||
return n, err
|
||||
}
|
||||
ve.SetInt(int64(i))
|
||||
return n, nil
|
||||
|
||||
case reflect.Int64:
|
||||
i, n, err := d.DecodeHyper()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetInt(i)
|
||||
return n, nil
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
|
||||
ui, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if ve.OverflowUint(uint64(ui)) {
|
||||
msg := fmt.Sprintf("unsigned integer too large to fit '%s'",
|
||||
ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrOverflow, msg, ui, nil)
|
||||
return n, err
|
||||
}
|
||||
ve.SetUint(uint64(ui))
|
||||
return n, nil
|
||||
|
||||
case reflect.Uint64:
|
||||
ui, n, err := d.DecodeUhyper()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetUint(ui)
|
||||
return n, nil
|
||||
|
||||
case reflect.Bool:
|
||||
b, n, err := d.DecodeBool()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetBool(b)
|
||||
return n, nil
|
||||
|
||||
case reflect.Float32:
|
||||
f, n, err := d.DecodeFloat()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetFloat(float64(f))
|
||||
return n, nil
|
||||
|
||||
case reflect.Float64:
|
||||
f, n, err := d.DecodeDouble()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetFloat(f)
|
||||
return n, nil
|
||||
|
||||
case reflect.String:
|
||||
s, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetString(s)
|
||||
return n, nil
|
||||
|
||||
case reflect.Array:
|
||||
n, err := d.decodeFixedArray(ve, false)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Slice:
|
||||
n, err := d.decodeArray(ve, false)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Struct:
|
||||
n, err := d.decodeStruct(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Map:
|
||||
n, err := d.decodeMap(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Interface:
|
||||
n, err := d.decodeInterface(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// The only unhandled types left are unsupported. At the time of this
|
||||
// writing the only remaining unsupported types that exist are
|
||||
// reflect.Uintptr and reflect.UnsafePointer.
|
||||
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// indirect dereferences pointers allocating them as needed until it reaches
|
||||
// a non-pointer. This allows transparent decoding through arbitrary levels
|
||||
// of indirection.
|
||||
func (d *Decoder) indirect(v reflect.Value) (reflect.Value, error) {
|
||||
rv := v
|
||||
for rv.Kind() == reflect.Ptr {
|
||||
// Allocate pointer if needed.
|
||||
isNil := rv.IsNil()
|
||||
if isNil && !rv.CanSet() {
|
||||
msg := fmt.Sprintf("unable to allocate pointer for '%v'",
|
||||
rv.Type().String())
|
||||
err := unmarshalError("indirect", ErrNotSettable, msg,
|
||||
nil, nil)
|
||||
return rv, err
|
||||
}
|
||||
if isNil {
|
||||
rv.Set(reflect.New(rv.Type().Elem()))
|
||||
}
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Decode operates identically to the Unmarshal function with the exception of
|
||||
// using the reader associated with the Decoder as the source of XDR-encoded
|
||||
// data instead of a user-supplied reader. See the Unmarhsal documentation for
|
||||
// specifics.
|
||||
func (d *Decoder) Decode(v interface{}) (int, error) {
|
||||
if v == nil {
|
||||
msg := "can't unmarshal to nil interface"
|
||||
return 0, unmarshalError("Unmarshal", ErrNilInterface, msg, nil,
|
||||
nil)
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v)
|
||||
if vv.Kind() != reflect.Ptr {
|
||||
msg := fmt.Sprintf("can't unmarshal to non-pointer '%v' - use "+
|
||||
"& operator", vv.Type().String())
|
||||
err := unmarshalError("Unmarshal", ErrBadArguments, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
if vv.IsNil() && !vv.CanSet() {
|
||||
msg := fmt.Sprintf("can't unmarshal to unsettable '%v' - use "+
|
||||
"& operator", vv.Type().String())
|
||||
err := unmarshalError("Unmarshal", ErrNotSettable, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return d.decode(vv)
|
||||
}
|
||||
|
||||
// NewDecoder returns a Decoder that can be used to manually decode XDR data
|
||||
// from a provided reader. Typically, Unmarshal should be used instead of
|
||||
// manually creating a Decoder.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
// NewDecoderLimited is identical to NewDecoder but it sets maxReadSize in
|
||||
// order to cap reads.
|
||||
func NewDecoderLimited(r io.Reader, maxSize uint) *Decoder {
|
||||
return &Decoder{r: r, maxReadSize: maxSize}
|
||||
}
|
||||
|
||||
// NewDecoderCustomTypes returns a decoder with support for custom types known
|
||||
// to the caller. The second parameter is a map of the type name to the decoder
|
||||
// routine. When the decoder finds a type matching one of the entries in the map
|
||||
// it will call the custom routine for that type.
|
||||
func NewDecoderCustomTypes(r io.Reader, maxSize uint, ct map[string]TypeDecoder) *Decoder {
|
||||
return &Decoder{r: r, maxReadSize: maxSize, customTypes: ct}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package xdr implements the data representation portion of the External Data
|
||||
Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes
|
||||
RFC 1832 and RFC 1014).
|
||||
|
||||
The XDR RFC defines both a data specification language and a data
|
||||
representation standard. This package implements methods to encode and decode
|
||||
XDR data per the data representation standard with the exception of 128-bit
|
||||
quadruple-precision floating points. It does not currently implement parsing of
|
||||
the data specification language. In other words, the ability to automatically
|
||||
generate Go code by parsing an XDR data specification file (typically .x
|
||||
extension) is not supported. In practice, this limitation of the package is
|
||||
fairly minor since it is largely unnecessary due to the reflection capabilities
|
||||
of Go as described below.
|
||||
|
||||
This package provides two approaches for encoding and decoding XDR data:
|
||||
|
||||
1) Marshal/Unmarshal functions which automatically map between XDR and Go types
|
||||
2) Individual Encoder/Decoder objects to manually work with XDR primitives
|
||||
|
||||
For the Marshal/Unmarshal functions, Go reflection capabilities are used to
|
||||
choose the type of the underlying XDR data based upon the Go type to encode or
|
||||
the target Go type to decode into. A description of how each type is mapped is
|
||||
provided below, however one important type worth reviewing is Go structs. In
|
||||
the case of structs, each exported field (first letter capitalized) is reflected
|
||||
and mapped in order. As a result, this means a Go struct with exported fields
|
||||
of the appropriate types listed in the expected order can be used to
|
||||
automatically encode / decode the XDR data thereby eliminating the need to write
|
||||
a lot of boilerplate code to encode/decode and error check each piece of XDR
|
||||
data as is typically required with C based XDR libraries.
|
||||
|
||||
Go Type to XDR Type Mappings
|
||||
|
||||
The following chart shows an overview of how Go types are mapped to XDR types
|
||||
for automatic marshalling and unmarshalling. The documentation for the Marshal
|
||||
and Unmarshal functions has specific details of how the mapping proceeds.
|
||||
|
||||
Go Type <-> XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int <-> XDR Integer
|
||||
uint8, uint16, uint32, uint <-> XDR Unsigned Integer
|
||||
int64 <-> XDR Hyper Integer
|
||||
uint64 <-> XDR Unsigned Hyper Integer
|
||||
bool <-> XDR Boolean
|
||||
float32 <-> XDR Floating-Point
|
||||
float64 <-> XDR Double-Precision Floating-Point
|
||||
string <-> XDR String
|
||||
byte <-> XDR Integer
|
||||
[]byte <-> XDR Variable-Length Opaque Data
|
||||
[#]byte <-> XDR Fixed-Length Opaque Data
|
||||
[]<type> <-> XDR Variable-Length Array
|
||||
[#]<type> <-> XDR Fixed-Length Array
|
||||
struct <-> XDR Structure
|
||||
map <-> XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time <-> XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic marshalling and unmarshalling of variable and fixed-length
|
||||
arrays of uint8s require a special struct tag `xdropaque:"false"`
|
||||
since byte slices and byte arrays are assumed to be opaque data and
|
||||
byte is a Go alias for uint8 thus indistinguishable under reflection
|
||||
* Channel, complex, and function types cannot be encoded
|
||||
* Interfaces without a concrete value cannot be encoded
|
||||
* Cyclic data structures are not supported and will result in infinite
|
||||
loops
|
||||
* Strings are marshalled and unmarshalled with UTF-8 character encoding
|
||||
which differs from the XDR specification of ASCII, however UTF-8 is
|
||||
backwards compatible with ASCII so this should rarely cause issues
|
||||
|
||||
|
||||
Encoding
|
||||
|
||||
To encode XDR data, use the Marshal function.
|
||||
func Marshal(w io.Writer, v interface{}) (int, error)
|
||||
|
||||
For example, given the following code snippet:
|
||||
|
||||
type ImageHeader struct {
|
||||
Signature [3]byte
|
||||
Version uint32
|
||||
IsGrayscale bool
|
||||
NumSections uint32
|
||||
}
|
||||
h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10}
|
||||
|
||||
var w bytes.Buffer
|
||||
bytesWritten, err := xdr.Marshal(&w, &h)
|
||||
// Error check elided
|
||||
|
||||
The result, encodedData, will then contain the following XDR encoded byte
|
||||
sequence:
|
||||
|
||||
0xAB, 0xCD, 0xEF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x0A
|
||||
|
||||
|
||||
In addition, while the automatic marshalling discussed above will work for the
|
||||
vast majority of cases, an Encoder object is provided that can be used to
|
||||
manually encode XDR primitives for complex scenarios where automatic
|
||||
reflection-based encoding won't work. The included examples provide a sample of
|
||||
manual usage via an Encoder.
|
||||
|
||||
|
||||
Decoding
|
||||
|
||||
To decode XDR data, use the Unmarshal function.
|
||||
func Unmarshal(r io.Reader, v interface{}) (int, error)
|
||||
|
||||
For example, given the following code snippet:
|
||||
|
||||
type ImageHeader struct {
|
||||
Signature [3]byte
|
||||
Version uint32
|
||||
IsGrayscale bool
|
||||
NumSections uint32
|
||||
}
|
||||
|
||||
// Using output from the Encoding section above.
|
||||
encodedData := []byte{
|
||||
0xAB, 0xCD, 0xEF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x0A,
|
||||
}
|
||||
|
||||
var h ImageHeader
|
||||
bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h)
|
||||
// Error check elided
|
||||
|
||||
The struct instance, h, will then contain the following values:
|
||||
|
||||
h.Signature = [3]byte{0xAB, 0xCD, 0xEF}
|
||||
h.Version = 2
|
||||
h.IsGrayscale = true
|
||||
h.NumSections = 10
|
||||
|
||||
In addition, while the automatic unmarshalling discussed above will work for the
|
||||
vast majority of cases, a Decoder object is provided that can be used to
|
||||
manually decode XDR primitives for complex scenarios where automatic
|
||||
reflection-based decoding won't work. The included examples provide a sample of
|
||||
manual usage via a Decoder.
|
||||
|
||||
Errors
|
||||
|
||||
All errors are either of type UnmarshalError or MarshalError. Both provide
|
||||
human-readable output as well as an ErrorCode field which can be inspected by
|
||||
sophisticated callers if necessary.
|
||||
|
||||
See the documentation of UnmarshalError, MarshalError, and ErrorCode for further
|
||||
details.
|
||||
*/
|
||||
package xdr
|
||||
+669
@@ -0,0 +1,669 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errIOEncode = "%s while encoding %d bytes"
|
||||
|
||||
/*
|
||||
Marshal writes the XDR encoding of v to writer w and returns the number of bytes
|
||||
written. It traverses v recursively and automatically indirects pointers
|
||||
through arbitrary depth to encode the actual value pointed to.
|
||||
|
||||
Marshal uses reflection to determine the type of the concrete value contained by
|
||||
v and performs a mapping of Go types to the underlying XDR types as follows:
|
||||
|
||||
Go Type -> XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int -> XDR Integer
|
||||
uint8, uint16, uint32, uint -> XDR Unsigned Integer
|
||||
int64 -> XDR Hyper Integer
|
||||
uint64 -> XDR Unsigned Hyper Integer
|
||||
bool -> XDR Boolean
|
||||
float32 -> XDR Floating-Point
|
||||
float64 -> XDR Double-Precision Floating-Point
|
||||
string -> XDR String
|
||||
byte -> XDR Integer
|
||||
[]byte -> XDR Variable-Length Opaque Data
|
||||
[#]byte -> XDR Fixed-Length Opaque Data
|
||||
[]<type> -> XDR Variable-Length Array
|
||||
[#]<type> -> XDR Fixed-Length Array
|
||||
struct -> XDR Structure
|
||||
map -> XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time -> XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic marshalling of variable and fixed-length arrays of uint8s
|
||||
requires a special struct tag `xdropaque:"false"` since byte slices and
|
||||
byte arrays are assumed to be opaque data and byte is a Go alias for uint8
|
||||
thus indistinguishable under reflection
|
||||
* Channel, complex, and function types cannot be encoded
|
||||
* Interfaces without a concrete value cannot be encoded
|
||||
* Cyclic data structures are not supported and will result in infinite loops
|
||||
* Strings are marshalled with UTF-8 character encoding which differs from
|
||||
the XDR specification of ASCII, however UTF-8 is backwards compatible with
|
||||
ASCII so this should rarely cause issues
|
||||
|
||||
If any issues are encountered during the marshalling process, a MarshalError is
|
||||
returned with a human readable description as well as an ErrorCode value for
|
||||
further inspection from sophisticated callers. Some potential issues are
|
||||
unsupported Go types, attempting to encode more opaque data than can be
|
||||
represented by a single opaque XDR entry, and exceeding max slice limitations.
|
||||
*/
|
||||
func Marshal(w io.Writer, v interface{}) (int, error) {
|
||||
enc := Encoder{w: w}
|
||||
return enc.Encode(v)
|
||||
}
|
||||
|
||||
// An Encoder wraps an io.Writer that will receive the XDR encoded byte stream.
|
||||
// See NewEncoder.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// EncodeInt writes the XDR encoded representation of the passed 32-bit signed
|
||||
// integer to the encapsulated writer and returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.1 - Integer
|
||||
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
|
||||
func (enc *Encoder) EncodeInt(v int32) (int, error) {
|
||||
var b [4]byte
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
|
||||
err := marshalError("EncodeInt", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeUint writes the XDR encoded representation of the passed 32-bit
|
||||
// unsigned integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.2 - Unsigned Integer
|
||||
// 32-bit big-endian unsigned integer in range [0, 4294967295]
|
||||
func (enc *Encoder) EncodeUint(v uint32) (int, error) {
|
||||
var b [4]byte
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
|
||||
err := marshalError("EncodeUint", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeEnum treats the passed 32-bit signed integer as an enumeration value
|
||||
// and, if it is in the list of passed valid enumeration values, writes the XDR
|
||||
// encoded representation of it to the encapsulated writer. It returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError is returned if the enumeration value is not one of the
|
||||
// provided valid values or if writing the data fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.3 - Enumeration
|
||||
// Represented as an XDR encoded signed integer
|
||||
func (enc *Encoder) EncodeEnum(v int32, validEnums map[int32]bool) (int, error) {
|
||||
if !validEnums[v] {
|
||||
err := marshalError("EncodeEnum", ErrBadEnumValue,
|
||||
"invalid enum", v, nil)
|
||||
return 0, err
|
||||
}
|
||||
return enc.EncodeInt(v)
|
||||
}
|
||||
|
||||
// EncodeBool writes the XDR encoded representation of the passed boolean to the
|
||||
// encapsulated writer and returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.4 - Boolean
|
||||
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
|
||||
func (enc *Encoder) EncodeBool(v bool) (int, error) {
|
||||
i := int32(0)
|
||||
if v == true {
|
||||
i = 1
|
||||
}
|
||||
return enc.EncodeInt(i)
|
||||
}
|
||||
|
||||
// EncodeHyper writes the XDR encoded representation of the passed 64-bit
|
||||
// signed integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Hyper Integer
|
||||
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
|
||||
func (enc *Encoder) EncodeHyper(v int64) (int, error) {
|
||||
var b [8]byte
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
|
||||
err := marshalError("EncodeHyper", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeUhyper writes the XDR encoded representation of the passed 64-bit
|
||||
// unsigned integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Unsigned Hyper Integer
|
||||
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
|
||||
func (enc *Encoder) EncodeUhyper(v uint64) (int, error) {
|
||||
var b [8]byte
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
|
||||
err := marshalError("EncodeUhyper", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeFloat writes the XDR encoded representation of the passed 32-bit
|
||||
// (single-precision) floating point to the encapsulated writer and returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.6 - Floating Point
|
||||
// 32-bit single-precision IEEE 754 floating point
|
||||
func (enc *Encoder) EncodeFloat(v float32) (int, error) {
|
||||
ui := math.Float32bits(v)
|
||||
return enc.EncodeUint(ui)
|
||||
}
|
||||
|
||||
// EncodeDouble writes the XDR encoded representation of the passed 64-bit
|
||||
// (double-precision) floating point to the encapsulated writer and returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.7 - Double-Precision Floating Point
|
||||
// 64-bit double-precision IEEE 754 floating point
|
||||
func (enc *Encoder) EncodeDouble(v float64) (int, error) {
|
||||
ui := math.Float64bits(v)
|
||||
return enc.EncodeUhyper(ui)
|
||||
}
|
||||
|
||||
// RFC Section 4.8 - Quadruple-Precision Floating Point
|
||||
// 128-bit quadruple-precision floating point
|
||||
// Not Implemented
|
||||
|
||||
// EncodeFixedOpaque treats the passed byte slice as opaque data of a fixed
|
||||
// size and writes the XDR encoded representation of it to the encapsulated
|
||||
// writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.9 - Fixed-Length Opaque Data
|
||||
// Fixed-length uninterpreted data zero-padded to a multiple of four
|
||||
func (enc *Encoder) EncodeFixedOpaque(v []byte) (int, error) {
|
||||
l := len(v)
|
||||
pad := (4 - (l % 4)) % 4
|
||||
|
||||
// Write the actual bytes.
|
||||
n, err := enc.w.Write(v)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), len(v))
|
||||
err := marshalError("EncodeFixedOpaque", ErrIO, msg, v[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write any padding if needed.
|
||||
if pad > 0 {
|
||||
b := make([]byte, pad)
|
||||
n2, err := enc.w.Write(b)
|
||||
n += n2
|
||||
if err != nil {
|
||||
written := make([]byte, l+n2)
|
||||
copy(written, v)
|
||||
copy(written[l:], b[:n2])
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), l+pad)
|
||||
err := marshalError("EncodeFixedOpaque", ErrIO, msg,
|
||||
written, err)
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeOpaque treats the passed byte slice as opaque data of a variable
|
||||
// size and writes the XDR encoded representation of it to the encapsulated
|
||||
// writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.10 - Variable-Length Opaque Data
|
||||
// Unsigned integer length followed by fixed opaque data of that length
|
||||
func (enc *Encoder) EncodeOpaque(v []byte) (int, error) {
|
||||
// Length of opaque data.
|
||||
n, err := enc.EncodeUint(uint32(len(v)))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.EncodeFixedOpaque(v)
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// EncodeString writes the XDR encoded representation of the passed string
|
||||
// to the encapsulated writer and returns the number of bytes written.
|
||||
// Character encoding is assumed to be UTF-8 and therefore ASCII compatible. If
|
||||
// the underlying character encoding is not compatible with this assumption, the
|
||||
// data can instead be written as variable-length opaque data (EncodeOpaque) and
|
||||
// manually converted as needed.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.11 - String
|
||||
// Unsigned integer length followed by bytes zero-padded to a multiple of four
|
||||
func (enc *Encoder) EncodeString(v string) (int, error) {
|
||||
// Length of string.
|
||||
n, err := enc.EncodeUint(uint32(len(v)))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.EncodeFixedOpaque([]byte(v))
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// encodeFixedArray writes the XDR encoded representation of each element
|
||||
// in the passed array represented by the reflection value to the encapsulated
|
||||
// writer and returns the number of bytes written. The ignoreOpaque flag
|
||||
// controls whether or not uint8 (byte) elements should be encoded individually
|
||||
// or as a fixed sequence of opaque data.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.12 - Fixed-Length Array
|
||||
// Individually XDR encoded array elements
|
||||
func (enc *Encoder) encodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
// Treat [#]byte (byte is alias for uint8) as opaque data unless ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// Create a slice of the underlying array for better efficiency
|
||||
// when possible. Can't create a slice of an unaddressable
|
||||
// value.
|
||||
if v.CanAddr() {
|
||||
return enc.EncodeFixedOpaque(v.Slice(0, v.Len()).Bytes())
|
||||
}
|
||||
|
||||
// When the underlying array isn't addressable fall back to
|
||||
// copying the array into a new slice. This is rather ugly, but
|
||||
// the inability to create a constant slice from an
|
||||
// unaddressable array is a limitation of Go.
|
||||
slice := make([]byte, v.Len(), v.Len())
|
||||
reflect.Copy(reflect.ValueOf(slice), v)
|
||||
return enc.EncodeFixedOpaque(slice)
|
||||
}
|
||||
|
||||
// Encode each array element.
|
||||
var n int
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
n2, err := enc.encode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// encodeArray writes an XDR encoded integer representing the number of
|
||||
// elements in the passed slice represented by the reflection value followed by
|
||||
// the XDR encoded representation of each element in slice to the encapsulated
|
||||
// writer and returns the number of bytes written. The ignoreOpaque flag
|
||||
// controls whether or not uint8 (byte) elements should be encoded individually
|
||||
// or as a variable sequence of opaque data.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.13 - Variable-Length Array
|
||||
// Unsigned integer length followed by individually XDR encoded array elements
|
||||
func (enc *Encoder) encodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
numItems := uint32(v.Len())
|
||||
n, err := enc.EncodeUint(numItems)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.encodeFixedArray(v, ignoreOpaque)
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// encodeStruct writes an XDR encoded representation of each value in the
|
||||
// exported fields of the struct represented by the passed reflection value to
|
||||
// the encapsulated writer and returns the number of bytes written. Pointers
|
||||
// are automatically indirected through arbitrary depth to encode the actual
|
||||
// value pointed to.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.14 - Structure
|
||||
// XDR encoded elements in the order of their declaration in the struct
|
||||
func (enc *Encoder) encodeStruct(v reflect.Value) (int, error) {
|
||||
var n int
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// Skip unexported fields and indirect through pointers.
|
||||
vtf := vt.Field(i)
|
||||
if vtf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
vf := v.Field(i)
|
||||
vf = enc.indirect(vf)
|
||||
|
||||
// Handle non-opaque data to []uint8 and [#]uint8 based on struct tag.
|
||||
tag := vtf.Tag.Get("xdropaque")
|
||||
if tag == "false" {
|
||||
switch vf.Kind() {
|
||||
case reflect.Slice:
|
||||
n2, err := enc.encodeArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
|
||||
case reflect.Array:
|
||||
n2, err := enc.encodeFixedArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Encode each struct field.
|
||||
n2, err := enc.encode(vf)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.15 - Discriminated Union
|
||||
// RFC Section 4.16 - Void
|
||||
// RFC Section 4.17 - Constant
|
||||
// RFC Section 4.18 - Typedef
|
||||
// RFC Section 4.19 - Optional data
|
||||
// RFC Sections 4.15 though 4.19 only apply to the data specification language
|
||||
// which is not implemented by this package. In the case of discriminated
|
||||
// unions, struct tags are used to perform a similar function.
|
||||
|
||||
// encodeMap treats the map represented by the passed reflection value as a
|
||||
// variable-length array of 2-element structures whose fields are of the same
|
||||
// type as the map keys and elements and writes its XDR encoded representation
|
||||
// to the encapsulated writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the elements.
|
||||
func (enc *Encoder) encodeMap(v reflect.Value) (int, error) {
|
||||
// Number of elements.
|
||||
n, err := enc.EncodeUint(uint32(v.Len()))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Encode each key and value according to their type.
|
||||
for _, key := range v.MapKeys() {
|
||||
n2, err := enc.encode(key)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err = enc.encode(v.MapIndex(key))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// encodeInterface examines the interface represented by the passed reflection
|
||||
// value to detect whether it is an interface that can be encoded if it is,
|
||||
// extracts the underlying value to pass back into the encode function for
|
||||
// encoding according to its type.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the interface.
|
||||
func (enc *Encoder) encodeInterface(v reflect.Value) (int, error) {
|
||||
if v.IsNil() || !v.CanInterface() {
|
||||
msg := fmt.Sprintf("can't encode nil interface")
|
||||
err := marshalError("encodeInterface", ErrNilInterface, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Extract underlying value from the interface and indirect through pointers.
|
||||
ve := reflect.ValueOf(v.Interface())
|
||||
ve = enc.indirect(ve)
|
||||
return enc.encode(ve)
|
||||
}
|
||||
|
||||
// encode is the main workhorse for marshalling via reflection. It uses
|
||||
// the passed reflection value to choose the XDR primitives to encode into
|
||||
// the encapsulated writer and returns the number of bytes written. It is a
|
||||
// recursive function, so cyclic data structures are not supported and will
|
||||
// result in an infinite loop.
|
||||
func (enc *Encoder) encode(v reflect.Value) (int, error) {
|
||||
if !v.IsValid() {
|
||||
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
|
||||
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Indirect through pointers to get at the concrete value.
|
||||
ve := enc.indirect(v)
|
||||
|
||||
// Handle time.Time values by encoding them as an RFC3339 formatted
|
||||
// string with nanosecond precision. Check the type string before
|
||||
// doing a full blown conversion to interface and type assertion since
|
||||
// checking a string is much quicker.
|
||||
if ve.Type().String() == "time.Time" && ve.CanInterface() {
|
||||
viface := ve.Interface()
|
||||
if tv, ok := viface.(time.Time); ok {
|
||||
return enc.EncodeString(tv.Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle native Go types.
|
||||
switch ve.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
|
||||
return enc.EncodeInt(int32(ve.Int()))
|
||||
|
||||
case reflect.Int64:
|
||||
return enc.EncodeHyper(ve.Int())
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
|
||||
return enc.EncodeUint(uint32(ve.Uint()))
|
||||
|
||||
case reflect.Uint64:
|
||||
return enc.EncodeUhyper(ve.Uint())
|
||||
|
||||
case reflect.Bool:
|
||||
return enc.EncodeBool(ve.Bool())
|
||||
|
||||
case reflect.Float32:
|
||||
return enc.EncodeFloat(float32(ve.Float()))
|
||||
|
||||
case reflect.Float64:
|
||||
return enc.EncodeDouble(ve.Float())
|
||||
|
||||
case reflect.String:
|
||||
return enc.EncodeString(ve.String())
|
||||
|
||||
case reflect.Array:
|
||||
return enc.encodeFixedArray(ve, false)
|
||||
|
||||
case reflect.Slice:
|
||||
return enc.encodeArray(ve, false)
|
||||
|
||||
case reflect.Struct:
|
||||
return enc.encodeStruct(ve)
|
||||
|
||||
case reflect.Map:
|
||||
return enc.encodeMap(ve)
|
||||
|
||||
case reflect.Interface:
|
||||
return enc.encodeInterface(ve)
|
||||
}
|
||||
|
||||
// The only unhandled types left are unsupported. At the time of this
|
||||
// writing the only remaining unsupported types that exist are
|
||||
// reflect.Uintptr and reflect.UnsafePointer.
|
||||
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
|
||||
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// indirect dereferences pointers until it reaches a non-pointer. This allows
|
||||
// transparent encoding through arbitrary levels of indirection.
|
||||
func (enc *Encoder) indirect(v reflect.Value) reflect.Value {
|
||||
rv := v
|
||||
for rv.Kind() == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// Encode operates identically to the Marshal function with the exception of
|
||||
// using the writer associated with the Encoder for the destination of the
|
||||
// XDR-encoded data instead of a user-supplied writer. See the Marshal
|
||||
// documentation for specifics.
|
||||
func (enc *Encoder) Encode(v interface{}) (int, error) {
|
||||
if v == nil {
|
||||
msg := "can't marshal nil interface"
|
||||
err := marshalError("Marshal", ErrNilInterface, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v)
|
||||
vve := vv
|
||||
for vve.Kind() == reflect.Ptr {
|
||||
if vve.IsNil() {
|
||||
msg := fmt.Sprintf("can't marshal nil pointer '%v'",
|
||||
vv.Type().String())
|
||||
err := marshalError("Marshal", ErrBadArguments, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
vve = vve.Elem()
|
||||
}
|
||||
|
||||
return enc.encode(vve)
|
||||
}
|
||||
|
||||
// NewEncoder returns an object that can be used to manually choose fields to
|
||||
// XDR encode to the passed writer w. Typically, Marshal should be used instead
|
||||
// of manually creating an Encoder. An Encoder, along with several of its
|
||||
// methods to encode XDR primitives, is exposed so it is possible to perform
|
||||
// manual encoding of data without relying on reflection should it be necessary
|
||||
// in complex scenarios where automatic reflection-based encoding won't work.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrorCode identifies a kind of error.
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
// ErrBadArguments indicates arguments passed to the function are not
|
||||
// what was expected.
|
||||
ErrBadArguments ErrorCode = iota
|
||||
|
||||
// ErrUnsupportedType indicates the Go type is not a supported type for
|
||||
// marshalling and unmarshalling XDR data.
|
||||
ErrUnsupportedType
|
||||
|
||||
// ErrBadEnumValue indicates an enumeration value is not in the list of
|
||||
// valid values.
|
||||
ErrBadEnumValue
|
||||
|
||||
// ErrNotSettable indicates an interface value cannot be written to.
|
||||
// This usually means the interface value was not passed with the &
|
||||
// operator, but it can also happen if automatic pointer allocation
|
||||
// fails.
|
||||
ErrNotSettable
|
||||
|
||||
// ErrOverflow indicates that the data in question is too large to fit
|
||||
// into the corresponding Go or XDR data type. For example, an integer
|
||||
// decoded from XDR that is too large to fit into a target type of int8,
|
||||
// or opaque data that exceeds the max length of a Go slice.
|
||||
ErrOverflow
|
||||
|
||||
// ErrNilInterface indicates an interface with no concrete type
|
||||
// information was encountered. Type information is necessary to
|
||||
// perform mapping between XDR and Go types.
|
||||
ErrNilInterface
|
||||
|
||||
// ErrIO indicates an error was encountered while reading or writing to
|
||||
// an io.Reader or io.Writer, respectively. The actual underlying error
|
||||
// will be available via the Err field of the MarshalError or
|
||||
// UnmarshalError struct.
|
||||
ErrIO
|
||||
|
||||
// ErrParseTime indicates an error was encountered while parsing an
|
||||
// RFC3339 formatted time value. The actual underlying error will be
|
||||
// available via the Err field of the UnmarshalError struct.
|
||||
ErrParseTime
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrBadArguments: "ErrBadArguments",
|
||||
ErrUnsupportedType: "ErrUnsupportedType",
|
||||
ErrBadEnumValue: "ErrBadEnumValue",
|
||||
ErrNotSettable: "ErrNotSettable",
|
||||
ErrOverflow: "ErrOverflow",
|
||||
ErrNilInterface: "ErrNilInterface",
|
||||
ErrIO: "ErrIO",
|
||||
ErrParseTime: "ErrParseTime",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if s := errorCodeStrings[e]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ErrorCode (%d)", e)
|
||||
}
|
||||
|
||||
// UnmarshalError describes a problem encountered while unmarshaling data.
|
||||
// Some potential issues are unsupported Go types, attempting to decode a value
|
||||
// which is too large to fit into a specified Go type, and exceeding max slice
|
||||
// limitations.
|
||||
type UnmarshalError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Func string // Function name
|
||||
Value interface{} // Value actually parsed where appropriate
|
||||
Description string // Human readable description of the issue
|
||||
Err error // The underlying error for IO errors
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e *UnmarshalError) Error() string {
|
||||
switch e.ErrorCode {
|
||||
case ErrBadEnumValue, ErrOverflow, ErrIO, ErrParseTime:
|
||||
return fmt.Sprintf("xdr:%s: %s - read: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
}
|
||||
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
|
||||
}
|
||||
|
||||
// unmarshalError creates an error given a set of arguments and will copy byte
|
||||
// slices into the Value field since they might otherwise be changed from from
|
||||
// the original value.
|
||||
func unmarshalError(f string, c ErrorCode, desc string, v interface{}, err error) *UnmarshalError {
|
||||
e := &UnmarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
|
||||
switch t := v.(type) {
|
||||
case []byte:
|
||||
slice := make([]byte, len(t))
|
||||
copy(slice, t)
|
||||
e.Value = slice
|
||||
default:
|
||||
e.Value = v
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// IsIO returns a boolean indicating whether the error is known to report that
|
||||
// the underlying reader or writer encountered an ErrIO.
|
||||
func IsIO(err error) bool {
|
||||
switch e := err.(type) {
|
||||
case *UnmarshalError:
|
||||
return e.ErrorCode == ErrIO
|
||||
case *MarshalError:
|
||||
return e.ErrorCode == ErrIO
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalError describes a problem encountered while marshaling data.
|
||||
// Some potential issues are unsupported Go types, attempting to encode more
|
||||
// opaque data than can be represented by a single opaque XDR entry, and
|
||||
// exceeding max slice limitations.
|
||||
type MarshalError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Func string // Function name
|
||||
Value interface{} // Value actually parsed where appropriate
|
||||
Description string // Human readable description of the issue
|
||||
Err error // The underlying error for IO errors
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e *MarshalError) Error() string {
|
||||
switch e.ErrorCode {
|
||||
case ErrIO:
|
||||
return fmt.Sprintf("xdr:%s: %s - wrote: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
case ErrBadEnumValue:
|
||||
return fmt.Sprintf("xdr:%s: %s - value: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
}
|
||||
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
|
||||
}
|
||||
|
||||
// marshalError creates an error given a set of arguments and will copy byte
|
||||
// slices into the Value field since they might otherwise be changed from from
|
||||
// the original value.
|
||||
func marshalError(f string, c ErrorCode, desc string, v interface{}, err error) *MarshalError {
|
||||
e := &MarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
|
||||
switch t := v.(type) {
|
||||
case []byte:
|
||||
slice := make([]byte, len(t))
|
||||
copy(slice, t)
|
||||
e.Value = slice
|
||||
default:
|
||||
e.Value = v
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
+890
@@ -0,0 +1,890 @@
|
||||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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 libvirt
|
||||
|
||||
// We'll use c-for-go to extract the consts and typedefs from the libvirt
|
||||
// sources so we don't have to duplicate them here.
|
||||
//go:generate scripts/gen-consts.sh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/event"
|
||||
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
"github.com/digitalocean/go-libvirt/socket"
|
||||
"github.com/digitalocean/go-libvirt/socket/dialers"
|
||||
)
|
||||
|
||||
// ErrEventsNotSupported is returned by Events() if event streams
|
||||
// are unsupported by either QEMU or libvirt.
|
||||
var ErrEventsNotSupported = errors.New("event monitor is not supported")
|
||||
|
||||
// ConnectURI defines a type for driver URIs for libvirt
|
||||
// the defined constants are *not* exhaustive as there are also options
|
||||
// e.g. to connect remote via SSH
|
||||
type ConnectURI string
|
||||
|
||||
const (
|
||||
// QEMUSystem connects to a QEMU system mode daemon
|
||||
QEMUSystem ConnectURI = "qemu:///system"
|
||||
// QEMUSession connects to a QEMU session mode daemon (unprivileged)
|
||||
QEMUSession ConnectURI = "qemu:///session"
|
||||
// XenSystem connects to a Xen system mode daemon
|
||||
XenSystem ConnectURI = "xen:///system"
|
||||
//TestDefault connect to default mock driver
|
||||
TestDefault ConnectURI = "test:///default"
|
||||
|
||||
// disconnectedTimeout is how long to wait for disconnect cleanup to
|
||||
// complete
|
||||
disconnectTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// Libvirt implements libvirt's remote procedure call protocol.
|
||||
type Libvirt struct {
|
||||
// socket connection
|
||||
socket *socket.Socket
|
||||
// closed after cleanup complete following the underlying connection to
|
||||
// libvirt being disconnected.
|
||||
disconnected chan struct{}
|
||||
|
||||
// method callbacks
|
||||
cmux sync.RWMutex
|
||||
callbacks map[int32]chan response
|
||||
|
||||
// event listeners
|
||||
emux sync.RWMutex
|
||||
events map[int32]*event.Stream
|
||||
|
||||
// next request serial number
|
||||
s int32
|
||||
}
|
||||
|
||||
// DomainEvent represents a libvirt domain event.
|
||||
type DomainEvent struct {
|
||||
CallbackID int32
|
||||
Domain Domain
|
||||
Event string
|
||||
Seconds uint64
|
||||
Microseconds uint32
|
||||
Padding uint8
|
||||
Details []byte
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID of a QEMU domain event.
|
||||
func (de DomainEvent) GetCallbackID() int32 {
|
||||
return de.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID of a libvirt lifecycle event.
|
||||
func (m DomainEventCallbackLifecycleMsg) GetCallbackID() int32 {
|
||||
return m.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackRebootMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackRtcChangeMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackWatchdogMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackIOErrorMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackIOErrorReasonMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackGraphicsMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackBlockJobMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackDiskChangeMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackTrayChangeMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackPmwakeupMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackPmsuspendMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackBalloonChangeMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackPmsuspendDiskMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackControlErrorMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackDeviceRemovedMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackTunableMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackDeviceAddedMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackAgentLifecycleMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackMigrationIterationMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackJobCompletedMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackDeviceRemovalFailedMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID.
|
||||
func (e *DomainEventCallbackMetadataChangeMsg) GetCallbackID() int32 {
|
||||
return e.CallbackID
|
||||
}
|
||||
|
||||
// qemuError represents a QEMU process error.
|
||||
type qemuError struct {
|
||||
Error struct {
|
||||
Class string `json:"class"`
|
||||
Description string `json:"desc"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// Capabilities returns an XML document describing the host's capabilties.
|
||||
func (l *Libvirt) Capabilities() ([]byte, error) {
|
||||
caps, err := l.ConnectGetCapabilities()
|
||||
return []byte(caps), err
|
||||
}
|
||||
|
||||
// called at connection time, authenticating with all supported auth types
|
||||
func (l *Libvirt) authenticate() error {
|
||||
// libvirt requires that we call auth-list prior to connecting,
|
||||
// even when no authentication is used.
|
||||
resp, err := l.AuthList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, auth := range resp {
|
||||
switch auth {
|
||||
case constants.AuthNone:
|
||||
case constants.AuthPolkit:
|
||||
_, err := l.AuthPolkit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Libvirt) initLibvirtComms(uri ConnectURI) error {
|
||||
payload := struct {
|
||||
Padding [3]byte
|
||||
Name string
|
||||
Flags uint32
|
||||
}{
|
||||
Padding: [3]byte{0x1, 0x0, 0x0},
|
||||
Name: string(uri),
|
||||
Flags: 0,
|
||||
}
|
||||
|
||||
buf, err := encode(&payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = l.authenticate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = l.request(constants.ProcConnectOpen, constants.Program, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectToURI establishes communication with the specified libvirt driver
|
||||
// The underlying libvirt socket connection will be created via the dialer.
|
||||
// Since the connection can be lost, the Disconnected function can be used
|
||||
// to monitor for a lost connection.
|
||||
func (l *Libvirt) ConnectToURI(uri ConnectURI) error {
|
||||
err := l.socket.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start watching the underlying socket connection immediately.
|
||||
// If we don't, and Libvirt goes away partway through initLibvirtComms,
|
||||
// then the callbacks that initLibvirtComms has registered will never
|
||||
// be closed, and therefore it will be stuck waiting for data from a
|
||||
// channel that will never arrive.
|
||||
go l.waitAndDisconnect()
|
||||
|
||||
err = l.initLibvirtComms(uri)
|
||||
if err != nil {
|
||||
l.socket.Disconnect()
|
||||
return err
|
||||
}
|
||||
|
||||
l.disconnected = make(chan struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect establishes communication with the libvirt server.
|
||||
// The underlying libvirt socket connection will be created via the dialer.
|
||||
// Since the connection can be lost, the Disconnected function can be used
|
||||
// to monitor for a lost connection.
|
||||
func (l *Libvirt) Connect() error {
|
||||
return l.ConnectToURI(QEMUSystem)
|
||||
}
|
||||
|
||||
// Disconnect shuts down communication with the libvirt server and closes the
|
||||
// underlying net.Conn.
|
||||
func (l *Libvirt) Disconnect() error {
|
||||
// Ordering is important here. We want to make sure the connection is closed
|
||||
// before unsubscribing and deregistering the events and requests, to
|
||||
// prevent new requests from racing.
|
||||
_, err := l.request(constants.ProcConnectClose, constants.Program, nil)
|
||||
|
||||
// syscall.EINVAL is returned by the socket pkg when things have already
|
||||
// been disconnected.
|
||||
if err != nil && err != syscall.EINVAL {
|
||||
return err
|
||||
}
|
||||
err = l.socket.Disconnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for the listen goroutine to detect the lost connection and clean up
|
||||
// to happen once it returns. Safeguard with a timeout.
|
||||
// Things not fully cleaned up is better than a deadlock.
|
||||
select {
|
||||
case <-l.disconnected:
|
||||
case <-time.After(disconnectTimeout):
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Disconnected allows callers to detect if the underlying connection
|
||||
// to libvirt has been closed. If the returned channel is closed, then
|
||||
// the connection to libvirt has been lost (or disconnected intentionally).
|
||||
func (l *Libvirt) Disconnected() <-chan struct{} {
|
||||
return l.disconnected
|
||||
}
|
||||
|
||||
// IsConnected indicates whether or not there is currently a connection to
|
||||
// libvirtd.
|
||||
func (l *Libvirt) IsConnected() bool {
|
||||
select {
|
||||
case <-l.Disconnected():
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Domains returns a list of all domains managed by libvirt.
|
||||
//
|
||||
// Deprecated: use ConnectListAllDomains instead.
|
||||
func (l *Libvirt) Domains() ([]Domain, error) {
|
||||
// these are the flags as passed by `virsh list --all`
|
||||
flags := ConnectListDomainsActive | ConnectListDomainsInactive
|
||||
domains, _, err := l.ConnectListAllDomains(1, flags)
|
||||
return domains, err
|
||||
}
|
||||
|
||||
// DomainState returns state of the domain managed by libvirt.
|
||||
//
|
||||
// Deprecated: use DomainGetState instead.
|
||||
func (l *Libvirt) DomainState(dom string) (DomainState, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return DomainNostate, err
|
||||
}
|
||||
|
||||
state, _, err := l.DomainGetState(d, 0)
|
||||
return DomainState(state), err
|
||||
}
|
||||
|
||||
// SubscribeQEMUEvents streams domain events until the provided context is
|
||||
// cancelled. If a problem is encountered setting up the event monitor
|
||||
// connection an error will be returned. Errors encountered during streaming
|
||||
// will cause the returned event channel to be closed. QEMU domain events.
|
||||
func (l *Libvirt) SubscribeQEMUEvents(ctx context.Context, dom string) (<-chan DomainEvent, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callbackID, err := l.QEMUConnectDomainMonitorEventRegister([]Domain{d}, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.QEMUProgram, callbackID)
|
||||
l.addStream(stream)
|
||||
ch := make(chan DomainEvent)
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeQEMUEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer close(ch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- *ev.(*DomainEvent)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// unsubscribeQEMUEvents stops the flow of events from QEMU through libvirt.
|
||||
func (l *Libvirt) unsubscribeQEMUEvents(stream *event.Stream) error {
|
||||
err := l.QEMUConnectDomainMonitorEventDeregister(stream.CallbackID)
|
||||
l.removeStream(stream.CallbackID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SubscribeEvents allows the caller to subscribe to any of the event types
|
||||
// supported by libvirt. The events will continue to be streamed until the
|
||||
// caller cancels the provided context. After canceling the context, callers
|
||||
// should wait until the channel is closed to be sure they're collected all the
|
||||
// events.
|
||||
func (l *Libvirt) SubscribeEvents(ctx context.Context, eventID DomainEventID,
|
||||
dom OptDomain) (<-chan interface{}, error) {
|
||||
|
||||
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(eventID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.QEMUProgram, callbackID)
|
||||
l.addStream(stream)
|
||||
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer func() { close(ch) }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- ev
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// unsubscribeEvents stops the flow of the specified events from libvirt. There
|
||||
// are two steps to this process: a call to libvirt to deregister our callback,
|
||||
// and then removing the callback from the list used by the `Route` function. If
|
||||
// the deregister call fails, we'll return the error, but still remove the
|
||||
// callback from the list. That's ok; if any events arrive after this point, the
|
||||
// Route function will drop them when it finds no registered handler.
|
||||
func (l *Libvirt) unsubscribeEvents(stream *event.Stream) error {
|
||||
err := l.ConnectDomainEventCallbackDeregisterAny(stream.CallbackID)
|
||||
l.removeStream(stream.CallbackID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// LifecycleEvents streams lifecycle events until the provided context is
|
||||
// cancelled. If a problem is encountered setting up the event monitor
|
||||
// connection, an error will be returned. Errors encountered during streaming
|
||||
// will cause the returned event channel to be closed.
|
||||
func (l *Libvirt) LifecycleEvents(ctx context.Context) (<-chan DomainEventLifecycleMsg, error) {
|
||||
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(DomainEventIDLifecycle), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.Program, callbackID)
|
||||
l.addStream(stream)
|
||||
|
||||
ch := make(chan DomainEventLifecycleMsg)
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer func() { close(ch) }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- ev.(*DomainEventCallbackLifecycleMsg).Msg
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// Run executes the given QAPI command against a domain's QEMU instance.
|
||||
// For a list of available QAPI commands, see:
|
||||
// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD
|
||||
func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := struct {
|
||||
Domain Domain
|
||||
Command []byte
|
||||
Flags uint32
|
||||
}{
|
||||
Domain: d,
|
||||
Command: cmd,
|
||||
Flags: 0,
|
||||
}
|
||||
|
||||
buf, err := encode(&payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := l.request(constants.QEMUProcDomainMonitorCommand, constants.QEMUProgram, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for QEMU process errors
|
||||
if err = getQEMUError(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := bytes.NewReader(res.Payload)
|
||||
dec := xdr.NewDecoder(r)
|
||||
data, _, err := dec.DecodeFixedOpaque(int32(r.Len()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// drop QMP control characters from start of line, and drop
|
||||
// any trailing NULL characters from the end
|
||||
return bytes.TrimRight(data[4:], "\x00"), nil
|
||||
}
|
||||
|
||||
// Secrets returns all secrets managed by the libvirt daemon.
|
||||
//
|
||||
// Deprecated: use ConnectListAllSecrets instead.
|
||||
func (l *Libvirt) Secrets() ([]Secret, error) {
|
||||
secrets, _, err := l.ConnectListAllSecrets(1, 0)
|
||||
return secrets, err
|
||||
}
|
||||
|
||||
// StoragePool returns the storage pool associated with the provided name.
|
||||
// An error is returned if the requested storage pool is not found.
|
||||
//
|
||||
// Deprecated: use StoragePoolLookupByName instead.
|
||||
func (l *Libvirt) StoragePool(name string) (StoragePool, error) {
|
||||
return l.StoragePoolLookupByName(name)
|
||||
}
|
||||
|
||||
// StoragePools returns a list of defined storage pools. Pools are filtered by
|
||||
// the provided flags. See StoragePools*.
|
||||
//
|
||||
// Deprecated: use ConnectListAllStoragePools instead.
|
||||
func (l *Libvirt) StoragePools(flags ConnectListAllStoragePoolsFlags) ([]StoragePool, error) {
|
||||
pools, _, err := l.ConnectListAllStoragePools(1, flags)
|
||||
return pools, err
|
||||
}
|
||||
|
||||
// Undefine undefines the domain specified by dom, e.g., 'prod-lb-01'.
|
||||
// The flags argument allows additional options to be specified such as
|
||||
// cleaning up snapshot metadata. For more information on available
|
||||
// flags, see DomainUndefine*.
|
||||
//
|
||||
// Deprecated: use DomainUndefineFlags instead.
|
||||
func (l *Libvirt) Undefine(dom string, flags DomainUndefineFlagsValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainUndefineFlags(d, flags)
|
||||
}
|
||||
|
||||
// Destroy destroys the domain specified by dom, e.g., 'prod-lb-01'.
|
||||
// The flags argument allows additional options to be specified such as
|
||||
// allowing a graceful shutdown with SIGTERM than SIGKILL.
|
||||
// For more information on available flags, see DomainDestroy*.
|
||||
//
|
||||
// Deprecated: use DomainDestroyFlags instead.
|
||||
func (l *Libvirt) Destroy(dom string, flags DomainDestroyFlagsValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainDestroyFlags(d, flags)
|
||||
}
|
||||
|
||||
// XML returns a domain's raw XML definition, akin to `virsh dumpxml <domain>`.
|
||||
// See DomainXMLFlag* for optional flags.
|
||||
//
|
||||
// Deprecated: use DomainGetXMLDesc instead.
|
||||
func (l *Libvirt) XML(dom string, flags DomainXMLFlags) ([]byte, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xml, err := l.DomainGetXMLDesc(d, flags)
|
||||
return []byte(xml), err
|
||||
}
|
||||
|
||||
// DefineXML defines a domain, but does not start it.
|
||||
//
|
||||
// Deprecated: use DomainDefineXMLFlags instead.
|
||||
func (l *Libvirt) DefineXML(x []byte, flags DomainDefineFlags) error {
|
||||
_, err := l.DomainDefineXMLFlags(string(x), flags)
|
||||
return err
|
||||
}
|
||||
|
||||
// Version returns the version of the libvirt daemon.
|
||||
//
|
||||
// Deprecated: use ConnectGetLibVersion instead.
|
||||
func (l *Libvirt) Version() (string, error) {
|
||||
ver, err := l.ConnectGetLibVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The version is provided as an int following this formula:
|
||||
// version * 1,000,000 + minor * 1000 + micro
|
||||
// See src/libvirt-host.c # virConnectGetLibVersion
|
||||
major := ver / 1000000
|
||||
ver %= 1000000
|
||||
minor := ver / 1000
|
||||
ver %= 1000
|
||||
micro := ver
|
||||
|
||||
versionString := fmt.Sprintf("%d.%d.%d", major, minor, micro)
|
||||
return versionString, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down a domain. Note that the guest OS may ignore the request.
|
||||
// If flags is set to 0 then the hypervisor will choose the method of shutdown it considers best.
|
||||
//
|
||||
// Deprecated: use DomainShutdownFlags instead.
|
||||
func (l *Libvirt) Shutdown(dom string, flags DomainShutdownFlagValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainShutdownFlags(d, flags)
|
||||
}
|
||||
|
||||
// Reboot reboots the domain. Note that the guest OS may ignore the request.
|
||||
// If flags is set to zero, then the hypervisor will choose the method of shutdown it considers best.
|
||||
//
|
||||
// Deprecated: use DomainReboot instead.
|
||||
func (l *Libvirt) Reboot(dom string, flags DomainRebootFlagValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainReboot(d, flags)
|
||||
}
|
||||
|
||||
// Reset resets domain immediately without any guest OS shutdown
|
||||
//
|
||||
// Deprecated: use DomainReset instead.
|
||||
func (l *Libvirt) Reset(dom string) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainReset(d, 0)
|
||||
}
|
||||
|
||||
// BlockLimit contains a name and value pair for a Get/SetBlockIOTune limit. The
|
||||
// Name field is the name of the limit (to see a list of the limits that can be
|
||||
// applied, execute the 'blkdeviotune' command on a VM in virsh). Callers can
|
||||
// use the QEMUBlockIO... constants below for the Name value. The Value field is
|
||||
// the limit to apply.
|
||||
type BlockLimit struct {
|
||||
Name string
|
||||
Value uint64
|
||||
}
|
||||
|
||||
// SetBlockIOTune changes the per-device block I/O tunables within a guest.
|
||||
// Parameters are the name of the VM, the name of the disk device to which the
|
||||
// limits should be applied, and 1 or more BlockLimit structs containing the
|
||||
// actual limits.
|
||||
//
|
||||
// The limits which can be applied here are enumerated in the QEMUBlockIO...
|
||||
// constants above, and you can also see the full list by executing the
|
||||
// 'blkdeviotune' command on a VM in virsh.
|
||||
//
|
||||
// Example usage:
|
||||
// SetBlockIOTune("vm-name", "vda", BlockLimit{libvirt.QEMUBlockIOWriteBytesSec, 1000000})
|
||||
//
|
||||
// Deprecated: use DomainSetBlockIOTune instead.
|
||||
func (l *Libvirt) SetBlockIOTune(dom string, disk string, limits ...BlockLimit) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := make([]TypedParam, len(limits))
|
||||
for ix, limit := range limits {
|
||||
tpval := NewTypedParamValueUllong(limit.Value)
|
||||
params[ix] = TypedParam{Field: limit.Name, Value: *tpval}
|
||||
}
|
||||
|
||||
return l.DomainSetBlockIOTune(d, disk, params, uint32(DomainAffectLive))
|
||||
}
|
||||
|
||||
// GetBlockIOTune returns a slice containing the current block I/O tunables for
|
||||
// a disk.
|
||||
//
|
||||
// Deprecated: use DomainGetBlockIOTune instead.
|
||||
func (l *Libvirt) GetBlockIOTune(dom string, disk string) ([]BlockLimit, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lims, _, err := l.DomainGetBlockIOTune(d, []string{disk}, 32, uint32(TypedParamStringOkay))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var limits []BlockLimit
|
||||
|
||||
// now decode each of the returned TypedParams. To do this we read the field
|
||||
// name and type, then use the type information to decode the value.
|
||||
for _, lim := range lims {
|
||||
var l BlockLimit
|
||||
name := lim.Field
|
||||
switch lim.Value.I.(type) {
|
||||
case uint64:
|
||||
l = BlockLimit{Name: name, Value: lim.Value.I.(uint64)}
|
||||
}
|
||||
limits = append(limits, l)
|
||||
}
|
||||
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
// lookup returns a domain as seen by libvirt.
|
||||
func (l *Libvirt) lookup(name string) (Domain, error) {
|
||||
return l.DomainLookupByName(name)
|
||||
}
|
||||
|
||||
// getQEMUError checks the provided response for QEMU process errors.
|
||||
// If an error is found, it is extracted an returned, otherwise nil.
|
||||
func getQEMUError(r response) error {
|
||||
pl := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoder(pl)
|
||||
|
||||
s, _, err := dec.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var e qemuError
|
||||
if err = json.Unmarshal([]byte(s), &e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Error.Description != "" {
|
||||
return errors.New(e.Error.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Libvirt) waitAndDisconnect() {
|
||||
// wait for the socket to indicate if/when it's been disconnected
|
||||
<-l.socket.Disconnected()
|
||||
|
||||
// close event streams
|
||||
l.removeAllStreams()
|
||||
|
||||
// Deregister all callbacks to prevent blocking on clients with
|
||||
// outstanding requests
|
||||
l.deregisterAll()
|
||||
|
||||
select {
|
||||
case <-l.disconnected:
|
||||
// l.disconnected is already closed, i.e., Libvirt.ConnectToURI
|
||||
// was unable to complete all phases of its connection and
|
||||
// so this hadn't been assigned to an open channel yet (it
|
||||
// is set to a closed channel in Libvirt.New*)
|
||||
//
|
||||
// Just return to avoid closing an already-closed channel.
|
||||
return
|
||||
default:
|
||||
// if we make it here then reading from l.disconnected is blocking,
|
||||
// which suggests that it is open and must be closed.
|
||||
}
|
||||
|
||||
close(l.disconnected)
|
||||
}
|
||||
|
||||
// NewWithDialer configures a new Libvirt object that can be used to perform
|
||||
// RPCs via libvirt's socket. The actual connection will not be established
|
||||
// until Connect is called. The same Libvirt object may be used to re-connect
|
||||
// multiple times.
|
||||
func NewWithDialer(dialer socket.Dialer) *Libvirt {
|
||||
l := &Libvirt{
|
||||
s: 0,
|
||||
disconnected: make(chan struct{}),
|
||||
callbacks: make(map[int32]chan response),
|
||||
events: make(map[int32]*event.Stream),
|
||||
}
|
||||
|
||||
l.socket = socket.New(dialer, l)
|
||||
|
||||
// we start with a closed channel since that indicates no connection
|
||||
close(l.disconnected)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// New configures a new Libvirt RPC connection.
|
||||
// This function only remains to retain backwards compatability.
|
||||
// When Libvirt's Connect function is called, the Dial will simply return the
|
||||
// connection passed in here and start a goroutine listening/reading from it.
|
||||
// If at any point the Disconnect function is called, any subsequent Connect
|
||||
// call will simply return an already closed connection.
|
||||
//
|
||||
// Deprecated: Please use NewWithDialer.
|
||||
func New(conn net.Conn) *Libvirt {
|
||||
return NewWithDialer(dialers.NewAlreadyConnected(conn))
|
||||
}
|
||||
|
||||
// NetworkUpdateCompat is a wrapper over NetworkUpdate which swaps `Command` and `Section` when needed.
|
||||
// This function must be used instead of NetworkUpdate to be sure that the
|
||||
// NetworkUpdate call works both with older and newer libvirtd connections.
|
||||
//
|
||||
// libvirt on-wire protocol had a bug for a long time where Command and Section
|
||||
// were reversed. It's been fixed in newer libvirt versions, and backported to
|
||||
// some older versions. This helper detects what argument order libvirtd expects
|
||||
// and makes the correct NetworkUpdate call.
|
||||
func (l *Libvirt) NetworkUpdateCompat(Net Network, Command NetworkUpdateCommand, Section NetworkUpdateSection, ParentIndex int32, XML string, Flags NetworkUpdateFlags) (err error) {
|
||||
// This is defined in libvirt/src/libvirt_internal.h and thus not available in go-libvirt autogenerated code
|
||||
const virDrvFeatureNetworkUpdateHasCorrectOrder = 16
|
||||
hasCorrectOrder, err := l.ConnectSupportsFeature(virDrvFeatureNetworkUpdateHasCorrectOrder)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to confirm argument order for NetworkUpdate: %w", err)
|
||||
}
|
||||
|
||||
// https://gitlab.com/libvirt/libvirt/-/commit/b0f78d626a18bcecae3a4d165540ab88bfbfc9ee
|
||||
if hasCorrectOrder == 0 {
|
||||
return l.NetworkUpdate(Net, uint32(Section), uint32(Command), ParentIndex, XML, Flags)
|
||||
}
|
||||
return l.NetworkUpdate(Net, uint32(Command), uint32(Section), ParentIndex, XML, Flags)
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Configuration file for c-for-go, which go-libvirt uses to translate the const
|
||||
# and type definitions from the C-language sources in the libvirt project into
|
||||
# Go. This file is used by the c-for-go binary (github.com/xlab/c-for-go), which
|
||||
# is called when 'go generate' is run. See libvirt.go for the command line used.
|
||||
---
|
||||
GENERATOR:
|
||||
PackageName: libvirt
|
||||
PackageLicense: |
|
||||
Copyright 2018 The go-libvirt Authors.
|
||||
|
||||
Licensed 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.
|
||||
Includes: []
|
||||
|
||||
PARSER:
|
||||
# We can't use environment variables here, but we don't want to process the
|
||||
# libvirt version installed in the system folders (if any). Instead we'll
|
||||
# rely on our caller to link the libvirt source directory to lv_source/, and
|
||||
# run on that code. This isn't ideal, but changes to c-for-go are needed to
|
||||
# fix it.
|
||||
IncludePaths: [./lv_source/include, ./lv_source/build/include]
|
||||
SourcesPaths:
|
||||
- libvirt/libvirt.h
|
||||
- libvirt/virterror.h
|
||||
|
||||
TRANSLATOR:
|
||||
ConstRules:
|
||||
defines: eval
|
||||
Rules:
|
||||
global:
|
||||
- {action: accept, from: "^vir"}
|
||||
post-global:
|
||||
- {action: replace, from: "^vir"}
|
||||
- {load: snakecase}
|
||||
# Follow golint's capitalization conventions.
|
||||
- {action: replace, from: "Api([A-Z]|$)", to: "API$1"}
|
||||
- {action: replace, from: "Cpu([A-Z]|$)", to: "CPU$1"}
|
||||
- {action: replace, from: "Dns([A-Z]|$)", to: "DNS$1"}
|
||||
- {action: replace, from: "Eof([A-Z]|$)", to: "EOF$1"}
|
||||
- {action: replace, from: "Id([A-Z]|$)", to: "ID$1"}
|
||||
- {action: replace, from: "Ip([A-Z]|$)", to: "IP$1"}
|
||||
- {action: replace, from: "Tls([A-Z]|$)", to: "TLS$1"}
|
||||
- {action: replace, from: "Uuid([A-Z]|$)", to: "UUID$1"}
|
||||
- {action: replace, from: "Uri([A-Z]|$)", to: "URI$1"}
|
||||
- {action: replace, from: "Vcpu([A-Z]|$)", to: "VCPU$1"}
|
||||
- {action: replace, from: "Xml([A-Z]|$)", to: "XML$1"}
|
||||
- {action: replace, from: "Rpc([A-Z]|$)", to: "RPC$1"}
|
||||
- {action: replace, from: "Ssh([A-Z]|$)", to: "SSH$1"}
|
||||
- {action: replace, from: "Http([A-Z]|$)", to: "HTTP$1"}
|
||||
- {transform: unexport, from: "^From"}
|
||||
const:
|
||||
- {action: accept, from: "^VIR_"}
|
||||
# Special case to prevent a collision with a type:
|
||||
- {action: replace, from: "^VIR_DOMAIN_JOB_OPERATION", to: "VIR_DOMAIN_JOB_OPERATION_STR"}
|
||||
- {transform: lower}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
//
|
||||
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
|
||||
//
|
||||
// To regenerate, run 'go generate' in internal/lvgen.
|
||||
//
|
||||
|
||||
package libvirt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
)
|
||||
|
||||
// References to prevent "imported and not used" errors.
|
||||
var (
|
||||
_ = bytes.Buffer{}
|
||||
_ = io.Copy
|
||||
_ = constants.Program
|
||||
_ = xdr.Unmarshal
|
||||
)
|
||||
|
||||
//
|
||||
// Typedefs:
|
||||
//
|
||||
|
||||
//
|
||||
// Enums:
|
||||
//
|
||||
// QEMUProcedure is libvirt's qemu_procedure
|
||||
type QEMUProcedure int32
|
||||
|
||||
//
|
||||
// Structs:
|
||||
//
|
||||
// QEMUDomainMonitorCommandArgs is libvirt's qemu_domain_monitor_command_args
|
||||
type QEMUDomainMonitorCommandArgs struct {
|
||||
Dom Domain
|
||||
Cmd string
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorCommandRet is libvirt's qemu_domain_monitor_command_ret
|
||||
type QEMUDomainMonitorCommandRet struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
// QEMUDomainAttachArgs is libvirt's qemu_domain_attach_args
|
||||
type QEMUDomainAttachArgs struct {
|
||||
PidValue uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainAttachRet is libvirt's qemu_domain_attach_ret
|
||||
type QEMUDomainAttachRet struct {
|
||||
Dom Domain
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommandArgs is libvirt's qemu_domain_agent_command_args
|
||||
type QEMUDomainAgentCommandArgs struct {
|
||||
Dom Domain
|
||||
Cmd string
|
||||
Timeout int32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommandRet is libvirt's qemu_domain_agent_command_ret
|
||||
type QEMUDomainAgentCommandRet struct {
|
||||
Result OptString
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegisterArgs is libvirt's qemu_connect_domain_monitor_event_register_args
|
||||
type QEMUConnectDomainMonitorEventRegisterArgs struct {
|
||||
Dom OptDomain
|
||||
Event OptString
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegisterRet is libvirt's qemu_connect_domain_monitor_event_register_ret
|
||||
type QEMUConnectDomainMonitorEventRegisterRet struct {
|
||||
CallbackID int32
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventDeregisterArgs is libvirt's qemu_connect_domain_monitor_event_deregister_args
|
||||
type QEMUConnectDomainMonitorEventDeregisterArgs struct {
|
||||
CallbackID int32
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorEventMsg is libvirt's qemu_domain_monitor_event_msg
|
||||
type QEMUDomainMonitorEventMsg struct {
|
||||
CallbackID int32
|
||||
Dom Domain
|
||||
Event string
|
||||
Seconds int64
|
||||
Micros uint32
|
||||
Details OptString
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// QEMUDomainMonitorCommand is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_COMMAND.
|
||||
func (l *Libvirt) QEMUDomainMonitorCommand(Dom Domain, Cmd string, Flags uint32) (rResult string, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainMonitorCommandArgs {
|
||||
Dom: Dom,
|
||||
Cmd: Cmd,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(1, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Result: string
|
||||
_, err = dec.Decode(&rResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainAttach is the go wrapper for QEMU_PROC_DOMAIN_ATTACH.
|
||||
func (l *Libvirt) QEMUDomainAttach(PidValue uint32, Flags uint32) (rDom Domain, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainAttachArgs {
|
||||
PidValue: PidValue,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(2, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Dom: Domain
|
||||
_, err = dec.Decode(&rDom)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommand is the go wrapper for QEMU_PROC_DOMAIN_AGENT_COMMAND.
|
||||
func (l *Libvirt) QEMUDomainAgentCommand(Dom Domain, Cmd string, Timeout int32, Flags uint32) (rResult OptString, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainAgentCommandArgs {
|
||||
Dom: Dom,
|
||||
Cmd: Cmd,
|
||||
Timeout: Timeout,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(3, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Result: OptString
|
||||
_, err = dec.Decode(&rResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER.
|
||||
func (l *Libvirt) QEMUConnectDomainMonitorEventRegister(Dom OptDomain, Event OptString, Flags uint32) (rCallbackID int32, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUConnectDomainMonitorEventRegisterArgs {
|
||||
Dom: Dom,
|
||||
Event: Event,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(4, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// CallbackID: int32
|
||||
_, err = dec.Decode(&rCallbackID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventDeregister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER.
|
||||
func (l *Libvirt) QEMUConnectDomainMonitorEventDeregister(CallbackID int32) (err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUConnectDomainMonitorEventDeregisterArgs {
|
||||
CallbackID: CallbackID,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
_, err = l.requestStream(5, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorEvent is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_EVENT.
|
||||
func (l *Libvirt) QEMUDomainMonitorEvent() (err error) {
|
||||
var buf []byte
|
||||
|
||||
|
||||
_, err = l.requestStream(6, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+16494
File diff suppressed because it is too large
Load Diff
+481
@@ -0,0 +1,481 @@
|
||||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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 libvirt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/event"
|
||||
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
"github.com/digitalocean/go-libvirt/socket"
|
||||
)
|
||||
|
||||
// ErrUnsupported is returned if a procedure is not supported by libvirt
|
||||
var ErrUnsupported = errors.New("unsupported procedure requested")
|
||||
|
||||
// ErrInterrupted is returned if the socket is closed while waiting for the
|
||||
// result of a procedure call.
|
||||
var ErrInterrupted = errors.New("procedure interrupted while awaiting response")
|
||||
|
||||
// internal rpc response
|
||||
type response struct {
|
||||
Payload []byte
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// Error reponse from libvirt
|
||||
type Error struct {
|
||||
Code uint32
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// checkError is used to check whether an error is a libvirtError, and if it is,
|
||||
// whether its error code matches the one passed in. It will return false if
|
||||
// these conditions are not met.
|
||||
func checkError(err error, expectedError ErrorNumber) bool {
|
||||
for err != nil {
|
||||
e, ok := err.(Error)
|
||||
if ok {
|
||||
return e.Code == uint32(expectedError)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNotFound detects libvirt's ERR_NO_DOMAIN.
|
||||
func IsNotFound(err error) bool {
|
||||
return checkError(err, ErrNoDomain)
|
||||
}
|
||||
|
||||
// callback sends RPC responses to respective callers.
|
||||
func (l *Libvirt) callback(id int32, res response) {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
c, ok := l.callbacks[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
c <- res
|
||||
}
|
||||
|
||||
// Route sends incoming packets to their listeners.
|
||||
func (l *Libvirt) Route(h *socket.Header, buf []byte) {
|
||||
// Route events to their respective listener
|
||||
var event event.Event
|
||||
|
||||
switch h.Program {
|
||||
case constants.QEMUProgram:
|
||||
if h.Procedure != constants.QEMUProcDomainMonitorEvent {
|
||||
break
|
||||
}
|
||||
event = &DomainEvent{}
|
||||
case constants.Program:
|
||||
event = eventFromProcedureID(h.Procedure)
|
||||
}
|
||||
|
||||
if event != nil {
|
||||
err := eventDecoder(buf, event)
|
||||
if err != nil { // event was malformed, drop.
|
||||
return
|
||||
}
|
||||
|
||||
l.stream(event)
|
||||
return
|
||||
}
|
||||
|
||||
// send response to caller
|
||||
l.callback(h.Serial, response{Payload: buf, Status: h.Status})
|
||||
}
|
||||
|
||||
func eventFromProcedureID(procID uint32) event.Event {
|
||||
switch procID {
|
||||
case constants.ProcDomainEventCallbackLifecycle:
|
||||
return &DomainEventCallbackLifecycleMsg{}
|
||||
case constants.ProcDomainEventCallbackReboot:
|
||||
return &DomainEventCallbackRebootMsg{}
|
||||
case constants.ProcDomainEventCallbackRtcChange:
|
||||
return &DomainEventCallbackRtcChangeMsg{}
|
||||
case constants.ProcDomainEventCallbackWatchdog:
|
||||
return &DomainEventCallbackWatchdogMsg{}
|
||||
case constants.ProcDomainEventCallbackIOError:
|
||||
return &DomainEventCallbackIOErrorMsg{}
|
||||
case constants.ProcDomainEventCallbackIOErrorReason:
|
||||
return &DomainEventCallbackIOErrorReasonMsg{}
|
||||
case constants.ProcDomainEventCallbackGraphics:
|
||||
return &DomainEventCallbackGraphicsMsg{}
|
||||
case constants.ProcDomainEventCallbackBlockJob:
|
||||
return &DomainEventCallbackBlockJobMsg{}
|
||||
case constants.ProcDomainEventCallbackDiskChange:
|
||||
return &DomainEventCallbackDiskChangeMsg{}
|
||||
case constants.ProcDomainEventCallbackTrayChange:
|
||||
return &DomainEventCallbackTrayChangeMsg{}
|
||||
case constants.ProcDomainEventCallbackPmwakeup:
|
||||
return &DomainEventCallbackPmwakeupMsg{}
|
||||
case constants.ProcDomainEventCallbackPmsuspend:
|
||||
return &DomainEventCallbackPmsuspendMsg{}
|
||||
case constants.ProcDomainEventCallbackBalloonChange:
|
||||
return &DomainEventCallbackBalloonChangeMsg{}
|
||||
case constants.ProcDomainEventCallbackPmsuspendDisk:
|
||||
return &DomainEventCallbackPmsuspendDiskMsg{}
|
||||
case constants.ProcDomainEventCallbackControlError:
|
||||
return &DomainEventCallbackControlErrorMsg{}
|
||||
case constants.ProcDomainEventCallbackDeviceRemoved:
|
||||
return &DomainEventCallbackDeviceRemovedMsg{}
|
||||
case constants.ProcDomainEventCallbackTunable:
|
||||
return &DomainEventCallbackTunableMsg{}
|
||||
case constants.ProcDomainEventCallbackDeviceAdded:
|
||||
return &DomainEventCallbackDeviceAddedMsg{}
|
||||
case constants.ProcDomainEventCallbackAgentLifecycle:
|
||||
return &DomainEventCallbackAgentLifecycleMsg{}
|
||||
case constants.ProcDomainEventCallbackMigrationIteration:
|
||||
return &DomainEventCallbackMigrationIterationMsg{}
|
||||
case constants.ProcDomainEventCallbackJobCompleted:
|
||||
return &DomainEventCallbackJobCompletedMsg{}
|
||||
case constants.ProcDomainEventCallbackDeviceRemovalFailed:
|
||||
return &DomainEventCallbackDeviceRemovalFailedMsg{}
|
||||
case constants.ProcDomainEventCallbackMetadataChange:
|
||||
return &DomainEventCallbackMetadataChangeMsg{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serial provides atomic access to the next sequential request serial number.
|
||||
func (l *Libvirt) serial() int32 {
|
||||
return atomic.AddInt32(&l.s, 1)
|
||||
}
|
||||
|
||||
// stream decodes and relays domain events to their respective listener.
|
||||
func (l *Libvirt) stream(e event.Event) {
|
||||
l.emux.RLock()
|
||||
defer l.emux.RUnlock()
|
||||
|
||||
q, ok := l.events[e.GetCallbackID()]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
q.Push(e)
|
||||
}
|
||||
|
||||
// addStream configures the routing for an event stream.
|
||||
func (l *Libvirt) addStream(s *event.Stream) {
|
||||
l.emux.Lock()
|
||||
defer l.emux.Unlock()
|
||||
|
||||
l.events[s.CallbackID] = s
|
||||
}
|
||||
|
||||
// removeStream deletes an event stream. The caller should first notify libvirt
|
||||
// to stop sending events for this stream. Subsequent calls to removeStream are
|
||||
// idempotent and return nil.
|
||||
func (l *Libvirt) removeStream(id int32) error {
|
||||
l.emux.Lock()
|
||||
defer l.emux.Unlock()
|
||||
|
||||
// if the event is already removed, just return nil
|
||||
q, ok := l.events[id]
|
||||
if ok {
|
||||
delete(l.events, id)
|
||||
q.Shutdown()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeAllStreams deletes all event streams. This is meant to be used to
|
||||
// clean up only once the underlying connection to libvirt is disconnected and
|
||||
// thus does not attempt to notify libvirt to stop sending events.
|
||||
func (l *Libvirt) removeAllStreams() {
|
||||
l.emux.Lock()
|
||||
defer l.emux.Unlock()
|
||||
|
||||
for _, ev := range l.events {
|
||||
ev.Shutdown()
|
||||
delete(l.events, ev.CallbackID)
|
||||
}
|
||||
}
|
||||
|
||||
// register configures a method response callback
|
||||
func (l *Libvirt) register(id int32, c chan response) {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
l.callbacks[id] = c
|
||||
}
|
||||
|
||||
// deregister destroys a method response callback. It is the responsibility of
|
||||
// the caller to manage locking (l.cmux) during this call.
|
||||
func (l *Libvirt) deregister(id int32) {
|
||||
_, ok := l.callbacks[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
close(l.callbacks[id])
|
||||
delete(l.callbacks, id)
|
||||
}
|
||||
|
||||
// deregisterAll closes all waiting callback channels. This is used to clean up
|
||||
// if the connection to libvirt is lost. Callers waiting for responses will
|
||||
// return an error when the response channel is closed, rather than just
|
||||
// hanging.
|
||||
func (l *Libvirt) deregisterAll() {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
for id := range l.callbacks {
|
||||
l.deregister(id)
|
||||
}
|
||||
}
|
||||
|
||||
// request performs a libvirt RPC request.
|
||||
// returns response returned by server.
|
||||
// if response is not OK, decodes error from it and returns it.
|
||||
func (l *Libvirt) request(proc uint32, program uint32, payload []byte) (response, error) {
|
||||
return l.requestStream(proc, program, payload, nil, nil)
|
||||
}
|
||||
|
||||
// requestStream performs a libvirt RPC request. The `out` and `in` parameters
|
||||
// are optional, and should be nil when RPC endpoints don't return a stream.
|
||||
func (l *Libvirt) requestStream(proc uint32, program uint32, payload []byte,
|
||||
out io.Reader, in io.Writer) (response, error) {
|
||||
serial := l.serial()
|
||||
c := make(chan response)
|
||||
|
||||
l.register(serial, c)
|
||||
defer func() {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
l.deregister(serial)
|
||||
}()
|
||||
|
||||
err := l.socket.SendPacket(serial, proc, program, payload, socket.Call,
|
||||
socket.StatusOK)
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
|
||||
resp, err := l.getResponse(c)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
abort := make(chan bool)
|
||||
outErr := make(chan error)
|
||||
go func() {
|
||||
outErr <- l.socket.SendStream(serial, proc, program, out, abort)
|
||||
}()
|
||||
|
||||
// Even without incoming stream server sends confirmation once all data is received
|
||||
resp, err = l.processIncomingStream(c, in)
|
||||
if err != nil {
|
||||
abort <- true
|
||||
return resp, err
|
||||
}
|
||||
|
||||
err = <-outErr
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
}
|
||||
|
||||
switch in {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return l.processIncomingStream(c, in)
|
||||
}
|
||||
}
|
||||
|
||||
// processIncomingStream is called once we've successfully sent a request to
|
||||
// libvirt. It writes the responses back to the stream passed by the caller
|
||||
// until libvirt sends a packet with statusOK or an error.
|
||||
func (l *Libvirt) processIncomingStream(c chan response, inStream io.Writer) (response, error) {
|
||||
for {
|
||||
resp, err := l.getResponse(c)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// StatusOK indicates end of stream
|
||||
if resp.Status == socket.StatusOK {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// FIXME: this smells.
|
||||
// StatusError is handled in getResponse, so this must be StatusContinue
|
||||
// StatusContinue is only valid here for stream packets
|
||||
// libvirtd breaks protocol and returns StatusContinue with an
|
||||
// empty response Payload when the stream finishes
|
||||
if len(resp.Payload) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
if inStream != nil {
|
||||
_, err = inStream.Write(resp.Payload)
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Libvirt) getResponse(c chan response) (response, error) {
|
||||
resp, ok := <-c
|
||||
|
||||
if !ok {
|
||||
// The channel was closed before a response was received. This means
|
||||
// that the socket was unexpectedly closed during the RPC call. In
|
||||
// this case, we must assume the worst, such as libvirt crashed while
|
||||
// attempting to execute the call.
|
||||
return resp, ErrInterrupted
|
||||
}
|
||||
|
||||
if resp.Status == socket.StatusError {
|
||||
return resp, decodeError(resp.Payload)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// encode XDR encodes the provided data.
|
||||
func encode(data interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
_, err := xdr.Marshal(&buf, data)
|
||||
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// decodeError extracts an error message from the provider buffer.
|
||||
func decodeError(buf []byte) error {
|
||||
dec := xdr.NewDecoder(bytes.NewReader(buf))
|
||||
|
||||
e := struct {
|
||||
Code uint32
|
||||
DomainID uint32
|
||||
Padding uint8
|
||||
Message string
|
||||
Level uint32
|
||||
}{}
|
||||
_, err := dec.Decode(&e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(e.Message, "unknown procedure") {
|
||||
return ErrUnsupported
|
||||
}
|
||||
|
||||
// if libvirt returns ERR_OK, ignore the error
|
||||
if ErrorNumber(e.Code) == ErrOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Error{Code: uint32(e.Code), Message: e.Message}
|
||||
}
|
||||
|
||||
// eventDecoder decodes an event from a xdr buffer.
|
||||
func eventDecoder(buf []byte, e interface{}) error {
|
||||
dec := xdr.NewDecoder(bytes.NewReader(buf))
|
||||
_, err := dec.Decode(e)
|
||||
return err
|
||||
}
|
||||
|
||||
type typedParamDecoder struct{}
|
||||
|
||||
// Decode decodes a TypedParam. These are part of the libvirt spec, and not xdr
|
||||
// proper. TypedParams contain a name, which is called Field for some reason,
|
||||
// and a Value, which itself has a "discriminant" - an integer enum encoding the
|
||||
// actual type, and a value, the length of which varies based on the actual
|
||||
// type.
|
||||
func (tpd typedParamDecoder) Decode(d *xdr.Decoder, v reflect.Value) (int, error) {
|
||||
// Get the name of the typed param first
|
||||
name, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
val, n2, err := tpd.decodeTypedParamValue(d)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
tp := &TypedParam{Field: name, Value: *val}
|
||||
v.Set(reflect.ValueOf(*tp))
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeTypedParamValue decodes the Value part of a TypedParam.
|
||||
func (typedParamDecoder) decodeTypedParamValue(d *xdr.Decoder) (*TypedParamValue, int, error) {
|
||||
// All TypedParamValues begin with a uint32 discriminant that tells us what
|
||||
// type they are.
|
||||
discriminant, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
var n2 int
|
||||
var tpv *TypedParamValue
|
||||
switch discriminant {
|
||||
case 1:
|
||||
var val int32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 2:
|
||||
var val uint32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 3:
|
||||
var val int64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 4:
|
||||
var val uint64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 5:
|
||||
var val float64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 6:
|
||||
var val int32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 7:
|
||||
var val string
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("invalid parameter type %v", discriminant)
|
||||
}
|
||||
n += n2
|
||||
|
||||
return tpv, n, err
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// AlreadyConnected implements a dialer interface for a connection that was
|
||||
// established prior to initializing the socket object. This exists solely
|
||||
// for backwards compatability with the previous implementation of Libvirt
|
||||
// that took an already established connection.
|
||||
type AlreadyConnected struct {
|
||||
c net.Conn
|
||||
}
|
||||
|
||||
// NewAlreadyConnected is a noop dialer to simply use a connection previously
|
||||
// established. This means any re-dial attempts simply won't work.
|
||||
func NewAlreadyConnected(c net.Conn) AlreadyConnected {
|
||||
return AlreadyConnected{c}
|
||||
}
|
||||
|
||||
// Dial just returns the connection previously established.
|
||||
// If at some point it is disconnected by the client, this obviously does *not*
|
||||
// re-dial and will simply return the already closed connection.
|
||||
func (a AlreadyConnected) Dial() (net.Conn, error) {
|
||||
return a.c, nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultSocket specifies the default path to the libvirt unix socket.
|
||||
defaultSocket = "/var/run/libvirt/libvirt-sock"
|
||||
|
||||
// defaultLocalTimeout specifies the default libvirt dial timeout.
|
||||
defaultLocalTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
// Local implements connecting to a local libvirtd over the unix socket.
|
||||
type Local struct {
|
||||
timeout time.Duration
|
||||
socket string
|
||||
}
|
||||
|
||||
// LocalOption is a function for setting local socket options.
|
||||
type LocalOption func(*Local)
|
||||
|
||||
// WithLocalTimeout sets the dial timeout.
|
||||
func WithLocalTimeout(timeout time.Duration) LocalOption {
|
||||
return func(l *Local) {
|
||||
l.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithSocket sets the path to the local libvirt socket.
|
||||
func WithSocket(socket string) LocalOption {
|
||||
return func(l *Local) {
|
||||
l.socket = socket
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocal is a default dialer to simply connect to a locally running libvirt's
|
||||
// socket.
|
||||
func NewLocal(opts ...LocalOption) *Local {
|
||||
l := &Local{
|
||||
timeout: defaultLocalTimeout,
|
||||
socket: defaultSocket,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(l)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Dial connects to a local socket
|
||||
func (l *Local) Dial() (net.Conn, error) {
|
||||
return net.DialTimeout("unix", l.socket, l.timeout)
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultRemotePort specifies the default libvirtd port.
|
||||
defaultRemotePort = "16509"
|
||||
|
||||
// defaultRemoteTimeout specifies the default libvirt dial timeout.
|
||||
defaultRemoteTimeout = 20 * time.Second
|
||||
)
|
||||
|
||||
// Remote implements connecting to a remote server's libvirt using tcp
|
||||
type Remote struct {
|
||||
timeout time.Duration
|
||||
host, port string
|
||||
}
|
||||
|
||||
// RemoteOption is a function for setting remote dialer options.
|
||||
type RemoteOption func(*Remote)
|
||||
|
||||
// WithRemoteTimeout sets the dial timeout.
|
||||
func WithRemoteTimeout(timeout time.Duration) RemoteOption {
|
||||
return func(r *Remote) {
|
||||
r.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// UsePort sets the port to dial for libirt on the target host server.
|
||||
func UsePort(port string) RemoteOption {
|
||||
return func(r *Remote) {
|
||||
r.port = port
|
||||
}
|
||||
}
|
||||
|
||||
// NewRemote is a dialer for connecting to libvirt running on another server.
|
||||
func NewRemote(hostAddr string, opts ...RemoteOption) *Remote {
|
||||
r := &Remote{
|
||||
timeout: defaultRemoteTimeout,
|
||||
host: hostAddr,
|
||||
port: defaultRemotePort,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Dial connects to libvirt running on another server.
|
||||
func (r *Remote) Dial() (net.Conn, error) {
|
||||
return net.DialTimeout(
|
||||
"tcp",
|
||||
net.JoinHostPort(r.host, r.port),
|
||||
r.timeout,
|
||||
)
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
)
|
||||
|
||||
const disconnectTimeout = 5 * time.Second
|
||||
|
||||
// request and response statuses
|
||||
const (
|
||||
// StatusOK is always set for method calls or events.
|
||||
// For replies it indicates successful completion of the method.
|
||||
// For streams it indicates confirmation of the end of file on the stream.
|
||||
StatusOK = iota
|
||||
|
||||
// StatusError for replies indicates that the method call failed
|
||||
// and error information is being returned. For streams this indicates
|
||||
// that not all data was sent and the stream has aborted.
|
||||
StatusError
|
||||
|
||||
// StatusContinue is only used for streams.
|
||||
// This indicates that further data packets will be following.
|
||||
StatusContinue
|
||||
)
|
||||
|
||||
// request and response types
|
||||
const (
|
||||
// Call is used when making calls to the remote server.
|
||||
Call = iota
|
||||
|
||||
// Reply indicates a server reply.
|
||||
Reply
|
||||
|
||||
// Message is an asynchronous notification.
|
||||
Message
|
||||
|
||||
// Stream represents a stream data packet.
|
||||
Stream
|
||||
|
||||
// CallWithFDs is used by a client to indicate the request has
|
||||
// arguments with file descriptors.
|
||||
CallWithFDs
|
||||
|
||||
// ReplyWithFDs is used by a server to indicate the request has
|
||||
// arguments with file descriptors.
|
||||
ReplyWithFDs
|
||||
)
|
||||
|
||||
// Dialer is an interface for connecting to libvirt's underlying socket.
|
||||
type Dialer interface {
|
||||
Dial() (net.Conn, error)
|
||||
}
|
||||
|
||||
// Router is an interface used to route packets to the appropriate clients.
|
||||
type Router interface {
|
||||
Route(*Header, []byte)
|
||||
}
|
||||
|
||||
// Socket represents a libvirt Socket and its connection state
|
||||
type Socket struct {
|
||||
dialer Dialer
|
||||
router Router
|
||||
|
||||
conn net.Conn
|
||||
reader *bufio.Reader
|
||||
writer *bufio.Writer
|
||||
// used to serialize any Socket writes and any updates to conn, r, or w
|
||||
mu *sync.Mutex
|
||||
|
||||
// disconnected is closed when the listen goroutine associated with a
|
||||
// Socket connection has returned.
|
||||
disconnected chan struct{}
|
||||
}
|
||||
|
||||
// packet represents a RPC request or response.
|
||||
type packet struct {
|
||||
// Size of packet, in bytes, including length.
|
||||
// Len + Header + Payload
|
||||
Len uint32
|
||||
Header Header
|
||||
}
|
||||
|
||||
// Global packet instance, for use with unsafe.Sizeof()
|
||||
var _p packet
|
||||
|
||||
// Header is a libvirt rpc packet header
|
||||
type Header struct {
|
||||
// Program identifier
|
||||
Program uint32
|
||||
|
||||
// Program version
|
||||
Version uint32
|
||||
|
||||
// Remote procedure identifier
|
||||
Procedure uint32
|
||||
|
||||
// Call type, e.g., Reply
|
||||
Type uint32
|
||||
|
||||
// Call serial number
|
||||
Serial int32
|
||||
|
||||
// Request status, e.g., StatusOK
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// New initializes a new type for managing the Socket.
|
||||
func New(dialer Dialer, router Router) *Socket {
|
||||
s := &Socket{
|
||||
dialer: dialer,
|
||||
router: router,
|
||||
disconnected: make(chan struct{}),
|
||||
mu: &sync.Mutex{},
|
||||
}
|
||||
|
||||
// we start with a closed channel since that indicates no connection
|
||||
close(s.disconnected)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Connect uses the dialer provided on creation to establish
|
||||
// underlying physical connection to the desired libvirt.
|
||||
func (s *Socket) Connect() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.isDisconnected() {
|
||||
return errors.New("already connected to socket")
|
||||
}
|
||||
conn, err := s.dialer.Dial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.conn = conn
|
||||
s.reader = bufio.NewReader(conn)
|
||||
s.writer = bufio.NewWriter(conn)
|
||||
s.disconnected = make(chan struct{})
|
||||
|
||||
go s.listenAndRoute()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect closes the Socket connection to libvirt and waits for the reader
|
||||
// gorouting to shut down.
|
||||
func (s *Socket) Disconnect() error {
|
||||
// just return if we're already disconnected
|
||||
if s.isDisconnected() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now we wait for the reader to return so as not to avoid it nil
|
||||
// referencing
|
||||
// Put this in a select,
|
||||
// and have it only nil out the conn value if it doesn't fail
|
||||
select {
|
||||
case <-s.disconnected:
|
||||
case <-time.After(disconnectTimeout):
|
||||
return errors.New("timed out waiting for Disconnect cleanup")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnected returns a channel that will be closed once the current
|
||||
// connection is closed. This can happen due to an explicit call to Disconnect
|
||||
// from the client, or due to non-temporary Read or Write errors encountered.
|
||||
func (s *Socket) Disconnected() <-chan struct{} {
|
||||
return s.disconnected
|
||||
}
|
||||
|
||||
// isDisconnected is a non-blocking function to query whether a connection
|
||||
// is disconnected or not.
|
||||
func (s *Socket) isDisconnected() bool {
|
||||
select {
|
||||
case <-s.disconnected:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// listenAndRoute reads packets from the Socket and calls the provided
|
||||
// Router function to route them
|
||||
func (s *Socket) listenAndRoute() {
|
||||
// only returns once it detects a non-temporary error related to the
|
||||
// underlying connection
|
||||
listen(s.reader, s.router)
|
||||
|
||||
// signal any clients listening that the connection has been disconnected
|
||||
close(s.disconnected)
|
||||
}
|
||||
|
||||
// listen processes incoming data and routes
|
||||
// responses to their respective callback handler.
|
||||
func listen(s io.Reader, router Router) {
|
||||
for {
|
||||
// response packet length
|
||||
length, err := pktlen(s)
|
||||
if err != nil {
|
||||
if isTemporary(err) {
|
||||
continue
|
||||
}
|
||||
// connection is no longer valid, so shutdown
|
||||
return
|
||||
}
|
||||
|
||||
// response header
|
||||
h, err := extractHeader(s)
|
||||
if err != nil {
|
||||
// invalid packet
|
||||
continue
|
||||
}
|
||||
|
||||
// payload: packet length minus what was previously read
|
||||
size := int(length) - int(unsafe.Sizeof(_p))
|
||||
buf := make([]byte, size)
|
||||
_, err = io.ReadFull(s, buf)
|
||||
if err != nil {
|
||||
// invalid packet
|
||||
continue
|
||||
}
|
||||
|
||||
// route response to caller
|
||||
router.Route(h, buf)
|
||||
}
|
||||
}
|
||||
|
||||
// isTemporary returns true if the error returned from a read is transient.
|
||||
// If the error type is an OpError, check whether the net connection
|
||||
// error condition is temporary (which means we can keep using the
|
||||
// connection).
|
||||
// Errors not of the net.OpError type tend to be things like io.EOF,
|
||||
// syscall.EINVAL, or io.ErrClosedPipe (i.e. all things that
|
||||
// indicate the connection in use is no longer valid.)
|
||||
func isTemporary(err error) bool {
|
||||
opErr, ok := err.(*net.OpError)
|
||||
if ok {
|
||||
return opErr.Temporary()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pktlen returns the length of an incoming RPC packet. Read errors will
|
||||
// result in a returned response length of 0 and a non-nil error.
|
||||
func pktlen(r io.Reader) (uint32, error) {
|
||||
buf := make([]byte, unsafe.Sizeof(_p.Len))
|
||||
|
||||
// extract the packet's length from the header
|
||||
_, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint32(buf), nil
|
||||
}
|
||||
|
||||
// extractHeader returns the decoded header from an incoming response.
|
||||
func extractHeader(r io.Reader) (*Header, error) {
|
||||
buf := make([]byte, unsafe.Sizeof(_p.Header))
|
||||
|
||||
// extract the packet's header from r
|
||||
_, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Header{
|
||||
Program: binary.BigEndian.Uint32(buf[0:4]),
|
||||
Version: binary.BigEndian.Uint32(buf[4:8]),
|
||||
Procedure: binary.BigEndian.Uint32(buf[8:12]),
|
||||
Type: binary.BigEndian.Uint32(buf[12:16]),
|
||||
Serial: int32(binary.BigEndian.Uint32(buf[16:20])),
|
||||
Status: binary.BigEndian.Uint32(buf[20:24]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendPacket sends a packet to libvirt on the socket connection.
|
||||
func (s *Socket) SendPacket(
|
||||
serial int32,
|
||||
proc uint32,
|
||||
program uint32,
|
||||
payload []byte,
|
||||
typ uint32,
|
||||
status uint32,
|
||||
) error {
|
||||
p := packet{
|
||||
Header: Header{
|
||||
Program: program,
|
||||
Version: constants.ProtocolVersion,
|
||||
Procedure: proc,
|
||||
Type: typ,
|
||||
Serial: serial,
|
||||
Status: status,
|
||||
},
|
||||
}
|
||||
|
||||
size := int(unsafe.Sizeof(p.Len)) + int(unsafe.Sizeof(p.Header))
|
||||
if payload != nil {
|
||||
size += len(payload)
|
||||
}
|
||||
p.Len = uint32(size)
|
||||
|
||||
if s.isDisconnected() {
|
||||
// this mirrors what a lot of net code return on use of a no
|
||||
// longer valid connection
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
err := binary.Write(s.writer, binary.BigEndian, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write payload
|
||||
if payload != nil {
|
||||
err = binary.Write(s.writer, binary.BigEndian, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.writer.Flush()
|
||||
}
|
||||
|
||||
// SendStream sends a stream of packets to libvirt on the socket connection.
|
||||
func (s *Socket) SendStream(serial int32, proc uint32, program uint32,
|
||||
stream io.Reader, abort chan bool) error {
|
||||
// Keep total packet length under 4 MiB to follow possible limitation in libvirt server code
|
||||
buf := make([]byte, 4*MiB-unsafe.Sizeof(_p))
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return s.SendPacket(serial, proc, program, nil, Stream, StatusError)
|
||||
default:
|
||||
}
|
||||
n, err := stream.Read(buf)
|
||||
if n > 0 {
|
||||
err2 := s.SendPacket(serial, proc, program, buf[:n], Stream, StatusContinue)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return s.SendPacket(serial, proc, program, nil, Stream, StatusOK)
|
||||
}
|
||||
// keep original error
|
||||
err2 := s.SendPacket(serial, proc, program, nil, Stream, StatusError)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2016 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
// This module provides different units of measurement to make other
|
||||
// code more readable.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
// B - byte
|
||||
B = 1
|
||||
// KiB - kibibyte
|
||||
KiB = 1024 * B
|
||||
// MiB - mebibyte
|
||||
MiB = 1024 * KiB
|
||||
)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Agent
|
||||
|
||||
Agent service provides a barebones HTTP and gRPC API and Service interface implementation for the development of the agent service.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ---------------------- | ------------------------------------------------------ | ------------------------------ |
|
||||
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | info |
|
||||
| AGENT_HTTP_HOST | Agent service HTTP host | "" |
|
||||
| AGENT_HTTP_PORT | Agent service HTTP port | 9031 |
|
||||
| AGENT_HTTP_SERVER_CERT | Path to HTTP server certificate in pem format | "" |
|
||||
| AGENT_HTTP_SERVER_KEY | Path to HTTP server key in pem format | "" |
|
||||
| AGENT_GRPC_HOST | Agent service gRPC host | "" |
|
||||
| AGENT_GRPC_PORT | Agent service gRPC port | 7002 |
|
||||
| AGENT_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
|
||||
| AGENT_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
|
||||
| AGENT_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
|
||||
|
||||
## Deployment
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
```bash
|
||||
# download the latest version of the service
|
||||
go get github.com/ultravioletrs/agent
|
||||
|
||||
cd $GOPATH/src/github.com/ultravioletrs/agent
|
||||
|
||||
# compile the agent
|
||||
make agent
|
||||
|
||||
# set the environment variables and run the service
|
||||
./build/cocos-agent
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For more information about service capabilities and its usage, please check out the [README documentation](../README.md).
|
||||
+593
@@ -0,0 +1,593 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v4.23.3
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type RunRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Computation []byte `protobuf:"bytes,1,opt,name=computation,proto3" json:"computation,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RunRequest) Reset() {
|
||||
*x = RunRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RunRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RunRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RunRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetComputation() []byte {
|
||||
if x != nil {
|
||||
return x.Computation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RunResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Computation string `protobuf:"bytes,1,opt,name=Computation,proto3" json:"Computation,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RunResponse) Reset() {
|
||||
*x = RunResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RunResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RunResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RunResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RunResponse) GetComputation() string {
|
||||
if x != nil {
|
||||
return x.Computation
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type AlgoRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AlgoRequest) Reset() {
|
||||
*x = AlgoRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AlgoRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AlgoRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AlgoRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AlgoRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AlgoRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *AlgoRequest) GetAlgorithm() []byte {
|
||||
if x != nil {
|
||||
return x.Algorithm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AlgoResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
AlgorithmID string `protobuf:"bytes,1,opt,name=algorithmID,proto3" json:"algorithmID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AlgoResponse) Reset() {
|
||||
*x = AlgoResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AlgoResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AlgoResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AlgoResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AlgoResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AlgoResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *AlgoResponse) GetAlgorithmID() string {
|
||||
if x != nil {
|
||||
return x.AlgorithmID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DataRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Dataset string `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DataRequest) Reset() {
|
||||
*x = DataRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DataRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DataRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DataRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DataRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DataRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *DataRequest) GetDataset() string {
|
||||
if x != nil {
|
||||
return x.Dataset
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DataResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DatasetID string `protobuf:"bytes,1,opt,name=datasetID,proto3" json:"datasetID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DataResponse) Reset() {
|
||||
*x = DataResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DataResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DataResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DataResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DataResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DataResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *DataResponse) GetDatasetID() string {
|
||||
if x != nil {
|
||||
return x.DatasetID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ResultRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *ResultRequest) Reset() {
|
||||
*x = ResultRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ResultRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ResultRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ResultRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ResultRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ResultRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
type ResultResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ResultResponse) Reset() {
|
||||
*x = ResultResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_agent_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ResultResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ResultResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ResultResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ResultResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ResultResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ResultResponse) GetFile() []byte {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_agent_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_agent_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x75,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70,
|
||||
0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63,
|
||||
0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2f, 0x0a, 0x0b, 0x52, 0x75,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x6f, 0x6d,
|
||||
0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x0b, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c,
|
||||
0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61,
|
||||
0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x22, 0x30, 0x0a, 0x0c, 0x41, 0x6c, 0x67, 0x6f,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6c, 0x67, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61,
|
||||
0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x44, 0x22, 0x27, 0x0a, 0x0b, 0x44, 0x61,
|
||||
0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x61, 0x74,
|
||||
0x61, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x61, 0x74, 0x61,
|
||||
0x73, 0x65, 0x74, 0x22, 0x2c, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x49, 0x44,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x49,
|
||||
0x44, 0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xdd, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x03, 0x52, 0x75, 0x6e,
|
||||
0x12, 0x11, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x41, 0x6c, 0x67,
|
||||
0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74,
|
||||
0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
||||
0x37, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_agent_agent_proto_rawDescOnce sync.Once
|
||||
file_agent_agent_proto_rawDescData = file_agent_agent_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_agent_agent_proto_rawDescGZIP() []byte {
|
||||
file_agent_agent_proto_rawDescOnce.Do(func() {
|
||||
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_agent_proto_rawDescData)
|
||||
})
|
||||
return file_agent_agent_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_agent_agent_proto_goTypes = []interface{}{
|
||||
(*RunRequest)(nil), // 0: agent.RunRequest
|
||||
(*RunResponse)(nil), // 1: agent.RunResponse
|
||||
(*AlgoRequest)(nil), // 2: agent.AlgoRequest
|
||||
(*AlgoResponse)(nil), // 3: agent.AlgoResponse
|
||||
(*DataRequest)(nil), // 4: agent.DataRequest
|
||||
(*DataResponse)(nil), // 5: agent.DataResponse
|
||||
(*ResultRequest)(nil), // 6: agent.ResultRequest
|
||||
(*ResultResponse)(nil), // 7: agent.ResultResponse
|
||||
}
|
||||
var file_agent_agent_proto_depIdxs = []int32{
|
||||
0, // 0: agent.AgentService.Run:input_type -> agent.RunRequest
|
||||
2, // 1: agent.AgentService.Algo:input_type -> agent.AlgoRequest
|
||||
4, // 2: agent.AgentService.Data:input_type -> agent.DataRequest
|
||||
6, // 3: agent.AgentService.Result:input_type -> agent.ResultRequest
|
||||
1, // 4: agent.AgentService.Run:output_type -> agent.RunResponse
|
||||
3, // 5: agent.AgentService.Algo:output_type -> agent.AlgoResponse
|
||||
5, // 6: agent.AgentService.Data:output_type -> agent.DataResponse
|
||||
7, // 7: agent.AgentService.Result:output_type -> agent.ResultResponse
|
||||
4, // [4:8] is the sub-list for method output_type
|
||||
0, // [0:4] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_agent_agent_proto_init() }
|
||||
func file_agent_agent_proto_init() {
|
||||
if File_agent_agent_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_agent_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RunRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RunResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AlgoRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AlgoResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DataRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DataResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ResultRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ResultResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_agent_agent_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_agent_agent_proto_goTypes,
|
||||
DependencyIndexes: file_agent_agent_proto_depIdxs,
|
||||
MessageInfos: file_agent_agent_proto_msgTypes,
|
||||
}.Build()
|
||||
File_agent_agent_proto = out.File
|
||||
file_agent_agent_proto_rawDesc = nil
|
||||
file_agent_agent_proto_goTypes = nil
|
||||
file_agent_agent_proto_depIdxs = nil
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package agent;
|
||||
|
||||
option go_package = "./agent";
|
||||
|
||||
service AgentService {
|
||||
rpc Run(RunRequest) returns (RunResponse) {}
|
||||
rpc Algo(AlgoRequest) returns (AlgoResponse) {}
|
||||
rpc Data(DataRequest) returns (DataResponse) {}
|
||||
rpc Result(ResultRequest) returns (ResultResponse) {}
|
||||
}
|
||||
|
||||
message RunRequest { bytes computation = 1; }
|
||||
|
||||
message RunResponse { string Computation = 1; }
|
||||
|
||||
message AlgoRequest { bytes algorithm = 1; }
|
||||
|
||||
message AlgoResponse { string algorithmID = 1; }
|
||||
|
||||
message DataRequest { string dataset = 1; }
|
||||
|
||||
message DataResponse { string datasetID = 1; }
|
||||
|
||||
message ResultRequest {}
|
||||
|
||||
message ResultResponse {
|
||||
bytes file = 1;
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v4.23.3
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
AgentService_Run_FullMethodName = "/agent.AgentService/Run"
|
||||
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
|
||||
AgentService_Data_FullMethodName = "/agent.AgentService/Data"
|
||||
AgentService_Result_FullMethodName = "/agent.AgentService/Result"
|
||||
)
|
||||
|
||||
// AgentServiceClient is the client API for AgentService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type AgentServiceClient interface {
|
||||
Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error)
|
||||
Algo(ctx context.Context, in *AlgoRequest, opts ...grpc.CallOption) (*AlgoResponse, error)
|
||||
Data(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*DataResponse, error)
|
||||
Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (*ResultResponse, error)
|
||||
}
|
||||
|
||||
type agentServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient {
|
||||
return &agentServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error) {
|
||||
out := new(RunResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_Run_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) Algo(ctx context.Context, in *AlgoRequest, opts ...grpc.CallOption) (*AlgoResponse, error) {
|
||||
out := new(AlgoResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_Algo_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) Data(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*DataResponse, error) {
|
||||
out := new(DataResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_Data_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (*ResultResponse, error) {
|
||||
out := new(ResultResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_Result_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AgentServiceServer is the server API for AgentService service.
|
||||
// All implementations must embed UnimplementedAgentServiceServer
|
||||
// for forward compatibility
|
||||
type AgentServiceServer interface {
|
||||
Run(context.Context, *RunRequest) (*RunResponse, error)
|
||||
Algo(context.Context, *AlgoRequest) (*AlgoResponse, error)
|
||||
Data(context.Context, *DataRequest) (*DataResponse, error)
|
||||
Result(context.Context, *ResultRequest) (*ResultResponse, error)
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedAgentServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedAgentServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedAgentServiceServer) Run(context.Context, *RunRequest) (*RunResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Run not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) Algo(context.Context, *AlgoRequest) (*AlgoResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Algo not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) Data(context.Context, *DataRequest) (*DataResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Data not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) Result(context.Context, *ResultRequest) (*ResultResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Result not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
|
||||
|
||||
// UnsafeAgentServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AgentServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAgentServiceServer interface {
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer) {
|
||||
s.RegisterService(&AgentService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AgentService_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RunRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).Run(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_Run_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).Run(ctx, req.(*RunRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_Algo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AlgoRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).Algo(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_Algo_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).Algo(ctx, req.(*AlgoRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_Data_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DataRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).Data(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_Data_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).Data(ctx, req.(*DataRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_Result_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ResultRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).Result(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_Result_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).Result(ctx, req.(*ResultRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AgentService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "agent.AgentService",
|
||||
HandlerType: (*AgentServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Run",
|
||||
Handler: _AgentService_Run_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Algo",
|
||||
Handler: _AgentService_Algo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Data",
|
||||
Handler: _AgentService_Data_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Result",
|
||||
Handler: _AgentService_Result_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "agent/agent.proto",
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const svcName = "agent.AgentService"
|
||||
|
||||
type grpcClient struct {
|
||||
run endpoint.Endpoint
|
||||
algo endpoint.Endpoint
|
||||
data endpoint.Endpoint
|
||||
result endpoint.Endpoint
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClient returns new gRPC client instance.
|
||||
func NewClient(conn *grpc.ClientConn, timeout time.Duration) agent.AgentServiceClient {
|
||||
return &grpcClient{
|
||||
run: kitgrpc.NewClient(
|
||||
conn,
|
||||
svcName,
|
||||
"Run",
|
||||
encodeRunRequest,
|
||||
decodeRunResponse,
|
||||
agent.RunResponse{},
|
||||
).Endpoint(),
|
||||
algo: kitgrpc.NewClient(
|
||||
conn,
|
||||
svcName,
|
||||
"Algo",
|
||||
encodeAlgoRequest,
|
||||
decodeAlgoResponse,
|
||||
agent.AlgoResponse{},
|
||||
).Endpoint(),
|
||||
data: kitgrpc.NewClient(
|
||||
conn,
|
||||
svcName,
|
||||
"Data",
|
||||
encodeDataRequest,
|
||||
decodeDataResponse,
|
||||
agent.DataResponse{},
|
||||
).Endpoint(),
|
||||
result: kitgrpc.NewClient(
|
||||
conn,
|
||||
svcName,
|
||||
"Result",
|
||||
encodeResultRequest,
|
||||
decodeResultResponse,
|
||||
agent.ResultResponse{},
|
||||
).Endpoint(),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRunRequest is a transport/grpc.EncodeRequestFunc that
|
||||
// converts a user-domain runReq to a gRPC request.
|
||||
func encodeRunRequest(_ context.Context, request interface{}) (interface{}, error) {
|
||||
req, ok := request.(*runReq)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid request type: %T", request)
|
||||
}
|
||||
|
||||
return &agent.RunRequest{
|
||||
Computation: req.Computation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decodeRunResponse is a transport/grpc.DecodeResponseFunc that
|
||||
// converts a gRPC RunResponse to a user-domain response.
|
||||
func decodeRunResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
|
||||
response, ok := grpcResponse.(*agent.RunResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
|
||||
}
|
||||
return runRes{
|
||||
Computation: response.Computation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodeAlgoRequest is a transport/grpc.EncodeRequestFunc that
|
||||
// converts a user-domain algoReq to a gRPC request.
|
||||
func encodeAlgoRequest(_ context.Context, request interface{}) (interface{}, error) {
|
||||
req, ok := request.(*algoReq)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid request type: %T", request)
|
||||
}
|
||||
|
||||
return &agent.AlgoRequest{
|
||||
Algorithm: req.Algorithm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decodeAlgoResponse is a transport/grpc.DecodeResponseFunc that
|
||||
// converts a gRPC AlgoResponse to a user-domain response.
|
||||
func decodeAlgoResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
|
||||
response, ok := grpcResponse.(*agent.AlgoResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
|
||||
}
|
||||
|
||||
return algoRes{
|
||||
AlgorithmID: response.AlgorithmID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodeDataRequest is a transport/grpc.EncodeRequestFunc that
|
||||
// converts a user-domain dataReq to a gRPC request.
|
||||
func encodeDataRequest(_ context.Context, request interface{}) (interface{}, error) {
|
||||
req, ok := request.(*dataReq)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid request type: %T", request)
|
||||
}
|
||||
|
||||
return &agent.DataRequest{
|
||||
Dataset: req.Dataset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decodeDataResponse is a transport/grpc.DecodeResponseFunc that
|
||||
// converts a gRPC DataResponse to a user-domain response.
|
||||
func decodeDataResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
|
||||
response, ok := grpcResponse.(*agent.DataResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
|
||||
}
|
||||
|
||||
return dataRes{
|
||||
DatasetID: response.DatasetID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodeResultRequest is a transport/grpc.EncodeRequestFunc that
|
||||
// converts a user-domain resultReq to a gRPC request.
|
||||
func encodeResultRequest(_ context.Context, request interface{}) (interface{}, error) {
|
||||
// No request parameters needed for retrieving computation result file
|
||||
return &agent.ResultRequest{}, nil
|
||||
}
|
||||
|
||||
// decodeResultResponse is a transport/grpc.DecodeResponseFunc that
|
||||
// converts a gRPC ResultResponse to a user-domain response.
|
||||
func decodeResultResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
|
||||
response, ok := grpcResponse.(*agent.ResultResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
|
||||
}
|
||||
|
||||
return resultRes{
|
||||
File: response.File,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run implements the Run method of the agent.AgentServiceClient interface.
|
||||
func (c grpcClient) Run(ctx context.Context, request *agent.RunRequest, _ ...grpc.CallOption) (*agent.RunResponse, error) {
|
||||
ctx, close := context.WithTimeout(ctx, c.timeout)
|
||||
defer close()
|
||||
|
||||
res, err := c.run(ctx, &runReq{Computation: request.Computation})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runRes := res.(runRes)
|
||||
return &agent.RunResponse{Computation: runRes.Computation}, nil
|
||||
}
|
||||
|
||||
// Algo implements the Algo method of the agent.AgentServiceClient interface.
|
||||
func (c grpcClient) Algo(ctx context.Context, request *agent.AlgoRequest, _ ...grpc.CallOption) (*agent.AlgoResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
res, err := c.algo(ctx, &algoReq{Algorithm: request.Algorithm})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
algoRes := res.(algoRes)
|
||||
return &agent.AlgoResponse{AlgorithmID: algoRes.AlgorithmID}, nil
|
||||
}
|
||||
|
||||
// Data implements the Data method of the agent.AgentServiceClient interface.
|
||||
func (c grpcClient) Data(ctx context.Context, request *agent.DataRequest, _ ...grpc.CallOption) (*agent.DataResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
res, err := c.data(ctx, &dataReq{Dataset: request.Dataset})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataRes := res.(dataRes)
|
||||
return &agent.DataResponse{DatasetID: dataRes.DatasetID}, nil
|
||||
}
|
||||
|
||||
// Result implements the Result method of the agent.AgentServiceClient interface.
|
||||
func (c grpcClient) Result(ctx context.Context, request *agent.ResultRequest, _ ...grpc.CallOption) (*agent.ResultResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
res, err := c.result(ctx, &resultReq{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultRes := res.(resultRes)
|
||||
return &agent.ResultResponse{File: resultRes.File}, nil
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package grpc contains implementation of kit service gRPC API.
|
||||
package grpc
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
)
|
||||
|
||||
func runEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(runReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return runRes{}, err
|
||||
}
|
||||
|
||||
var computation agent.Computation
|
||||
err := json.Unmarshal(req.Computation, &computation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
computationStr, err := svc.Run(ctx, computation)
|
||||
if err != nil {
|
||||
return runRes{}, err
|
||||
}
|
||||
|
||||
return runRes{Computation: computationStr}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(algoReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return algoRes{}, err
|
||||
}
|
||||
|
||||
algorithmID, err := svc.Algo(ctx, req.Algorithm)
|
||||
if err != nil {
|
||||
return algoRes{}, err
|
||||
}
|
||||
|
||||
return algoRes{AlgorithmID: algorithmID}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dataEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(dataReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return dataRes{}, err
|
||||
}
|
||||
|
||||
datasetID, err := svc.Data(ctx, req.Dataset)
|
||||
if err != nil {
|
||||
return dataRes{}, err
|
||||
}
|
||||
|
||||
return dataRes{DatasetID: datasetID}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func resultEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(resultReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return resultRes{}, err
|
||||
}
|
||||
file, err := svc.Result(ctx)
|
||||
if err != nil {
|
||||
return resultRes{}, err
|
||||
}
|
||||
|
||||
return resultRes{File: file}, nil
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package grpc
|
||||
|
||||
import "errors"
|
||||
|
||||
type runReq struct {
|
||||
Computation []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
|
||||
}
|
||||
|
||||
func (req runReq) validate() error {
|
||||
if len(req.Computation) == 0 {
|
||||
return errors.New("algorithm binary is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type algoReq struct {
|
||||
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
|
||||
}
|
||||
|
||||
func (req algoReq) validate() error {
|
||||
if len(req.Algorithm) == 0 {
|
||||
return errors.New("algorithm binary is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dataReq struct {
|
||||
Dataset string `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
}
|
||||
|
||||
func (req dataReq) validate() error {
|
||||
if len(req.Dataset) == 0 {
|
||||
return errors.New("dataset CSV file is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type resultReq struct {
|
||||
// No request parameters needed for retrieving computation result file
|
||||
}
|
||||
|
||||
func (req resultReq) validate() error {
|
||||
// No request parameters to validate, so no validation logic needed
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package grpc
|
||||
|
||||
type runRes struct {
|
||||
Computation string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
type algoRes struct {
|
||||
AlgorithmID string `json:"algorithmId,omitempty"`
|
||||
}
|
||||
|
||||
type dataRes struct {
|
||||
DatasetID string `json:"datasetId,omitempty"`
|
||||
}
|
||||
|
||||
type resultRes struct {
|
||||
File []byte `json:"-"`
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
)
|
||||
|
||||
type grpcServer struct {
|
||||
run kitgrpc.Handler
|
||||
algo kitgrpc.Handler
|
||||
data kitgrpc.Handler
|
||||
result kitgrpc.Handler
|
||||
agent.UnimplementedAgentServiceServer
|
||||
}
|
||||
|
||||
// NewServer returns new AgentServiceServer instance.
|
||||
func NewServer(svc agent.Service) agent.AgentServiceServer {
|
||||
return &grpcServer{
|
||||
run: kitgrpc.NewServer(
|
||||
runEndpoint(svc),
|
||||
decodeRunRequest,
|
||||
encodeRunResponse,
|
||||
),
|
||||
algo: kitgrpc.NewServer(
|
||||
algoEndpoint(svc),
|
||||
decodeAlgoRequest,
|
||||
encodeAlgoResponse,
|
||||
),
|
||||
data: kitgrpc.NewServer(
|
||||
dataEndpoint(svc),
|
||||
decodeDataRequest,
|
||||
encodeDataResponse,
|
||||
),
|
||||
result: kitgrpc.NewServer(
|
||||
resultEndpoint(svc),
|
||||
decodeResultRequest,
|
||||
encodeResultResponse,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRunRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.RunRequest)
|
||||
|
||||
return runReq{
|
||||
Computation: req.Computation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeRunResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(runRes)
|
||||
return &agent.RunResponse{
|
||||
Computation: res.Computation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeAlgoRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.AlgoRequest)
|
||||
|
||||
return algoReq{
|
||||
Algorithm: req.Algorithm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeAlgoResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(algoRes)
|
||||
return &agent.AlgoResponse{
|
||||
AlgorithmID: res.AlgorithmID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeDataRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.DataRequest)
|
||||
|
||||
return dataReq{
|
||||
Dataset: req.Dataset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeDataResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(dataRes)
|
||||
return &agent.DataResponse{
|
||||
DatasetID: res.DatasetID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeResultRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
// No fields to extract from gRPC request, so returning an empty struct
|
||||
return resultReq{}, nil
|
||||
}
|
||||
|
||||
func encodeResultResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(resultRes)
|
||||
return &agent.ResultResponse{
|
||||
File: res.File,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Run(ctx context.Context, req *agent.RunRequest) (*agent.RunResponse, error) {
|
||||
_, res, err := s.run.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr := res.(*agent.RunResponse)
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Algo(ctx context.Context, req *agent.AlgoRequest) (*agent.AlgoResponse, error) {
|
||||
_, res, err := s.algo.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ar := res.(*agent.AlgoResponse)
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Data(ctx context.Context, req *agent.DataRequest) (*agent.DataResponse, error) {
|
||||
_, res, err := s.data.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dr := res.(*agent.DataResponse)
|
||||
return dr, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Result(ctx context.Context, req *agent.ResultRequest) (*agent.ResultResponse, error) {
|
||||
_, res, err := s.result.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr := res.(*agent.ResultResponse)
|
||||
return rr, nil
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package agent
|
||||
|
||||
import "time"
|
||||
|
||||
type Computation struct {
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
Name string `json:"name,omitempty" db:"name"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
Status string `json:"status,omitempty" db:"status"`
|
||||
Owner string `json:"owner,omitempty" db:"owner"`
|
||||
StartTime time.Time `json:"start_time,omitempty" db:"start_time"`
|
||||
EndTime time.Time `json:"end_time,omitempty" db:"end_time"`
|
||||
Datasets []string `json:"datasets,omitempty" db:"datasets"`
|
||||
Algorithms []string `json:"algorithms,omitempty" db:"algorithms"`
|
||||
DatasetProviders []string `json:"dataset_providers,omitempty" db:"dataset_providers"`
|
||||
AlgorithmProviders []string `json:"algorithm_providers,omitempty" db:"algorithm_providers"`
|
||||
ResultConsumers []string `json:"result_consumers,omitempty" db:"result_consumers"`
|
||||
Ttl int32 `json:"ttl,omitempty" db:"ttl"`
|
||||
Metadata Metadata `json:"metadata,omitempty" db:"metadata"`
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package things contains the domain concept definitions needed to support
|
||||
// Mainflux agent service functionality.
|
||||
|
||||
package agent
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMalformedEntity indicates malformed entity specification (e.g.
|
||||
// invalid username or password).
|
||||
ErrMalformedEntity = errors.New("malformed entity specification")
|
||||
|
||||
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
|
||||
// when accessing a protected resource.
|
||||
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
||||
)
|
||||
|
||||
type Metadata map[string]interface{}
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
type Service interface {
|
||||
Run(ctx context.Context, cmp Computation) (string, error)
|
||||
Algo(ctx context.Context, algorithm []byte) (string, error)
|
||||
Data(ctx context.Context, dataset string) (string, error)
|
||||
Result(ctx context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
type agentService struct {
|
||||
}
|
||||
|
||||
var _ Service = (*agentService)(nil)
|
||||
|
||||
// New instantiates the agent service implementation.
|
||||
func New() Service {
|
||||
return &agentService{}
|
||||
}
|
||||
|
||||
func (ks *agentService) Run(ctx context.Context, cmp Computation) (string, error) {
|
||||
cmpJSON, err := json.Marshal(cmp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(cmpJSON), nil // return the JSON string as the function's string return value
|
||||
}
|
||||
|
||||
func (as *agentService) Algo(ctx context.Context, algorithm []byte) (string, error) {
|
||||
// Implement the logic for the Algo method based on your requirements
|
||||
// Use the provided ctx and algorithm parameters as needed
|
||||
|
||||
// Perform some processing on the algorithm byte array
|
||||
// For example, generate a unique ID for the algorithm
|
||||
algorithmID := "algo123"
|
||||
|
||||
// Return the algorithm ID or an error
|
||||
return algorithmID, nil
|
||||
}
|
||||
|
||||
func (as *agentService) Data(ctx context.Context, dataset string) (string, error) {
|
||||
// Implement the logic for the Data method based on your requirements
|
||||
// Use the provided ctx and dataset parameters as needed
|
||||
|
||||
// Perform some processing on the dataset string
|
||||
// For example, generate a unique ID for the dataset
|
||||
datasetID := "dataset456"
|
||||
|
||||
// Return the dataset ID or an error
|
||||
return datasetID, nil
|
||||
}
|
||||
|
||||
func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
// Implement the logic for the Result method based on your requirements
|
||||
// Use the provided ctx parameter as needed
|
||||
|
||||
// Perform some processing to retrieve the computation result file
|
||||
// For example, read the file from storage or generate a dummy result
|
||||
result := []byte("This is the computation result file.")
|
||||
|
||||
// Return the result file or an error
|
||||
return result, nil
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/ultravioletrs/agent/agent"
|
||||
agentapi "github.com/ultravioletrs/agent/agent/api/grpc"
|
||||
)
|
||||
|
||||
// NewClient creates new agent gRPC client instance.
|
||||
func NewClient(cfg Config) (Client, agent.AgentServiceClient, error) {
|
||||
client, err := newClient(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return client, agentapi.NewClient(client.Connection(), cfg.Timeout), nil
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
gogrpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var (
|
||||
errGrpcConnect = errors.New("failed to connect to grpc server")
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ClientTLS bool `env:"CLIENT_TLS" envDefault:"false"`
|
||||
CACerts string `env:"CA_CERTS" envDefault:""`
|
||||
URL string `env:"URL" envDefault:"localhost:7020"`
|
||||
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Close closes gRPC connection.
|
||||
Close() error
|
||||
|
||||
// Secure is used for pretty printing TLS info.
|
||||
Secure() string
|
||||
|
||||
// Connection returns the gRPC connection.
|
||||
Connection() *gogrpc.ClientConn
|
||||
}
|
||||
|
||||
type client struct {
|
||||
*gogrpc.ClientConn
|
||||
cfg Config
|
||||
secure bool
|
||||
}
|
||||
|
||||
var _ Client = (*client)(nil)
|
||||
|
||||
func newClient(cfg Config) (Client, error) {
|
||||
conn, secure, err := connect(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client{
|
||||
ClientConn: conn,
|
||||
cfg: cfg,
|
||||
secure: secure,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) Close() error {
|
||||
if err := c.ClientConn.Close(); err != nil {
|
||||
return errors.Wrap(errGrpcClose, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) Secure() string {
|
||||
if c.secure {
|
||||
return "with TLS"
|
||||
}
|
||||
return "without TLS"
|
||||
}
|
||||
|
||||
func (c *client) Connection() *gogrpc.ClientConn {
|
||||
return c.ClientConn
|
||||
}
|
||||
|
||||
// connect creates new gRPC client and connect to gRPC server.
|
||||
func connect(cfg Config) (*gogrpc.ClientConn, bool, error) {
|
||||
var opts []gogrpc.DialOption
|
||||
secure := false
|
||||
tc := insecure.NewCredentials()
|
||||
|
||||
if cfg.ClientTLS && cfg.CACerts != "" {
|
||||
var err error
|
||||
tc, err = credentials.NewClientTLSFromFile(cfg.CACerts, "")
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
secure = true
|
||||
}
|
||||
|
||||
opts = append(opts, gogrpc.WithTransportCredentials(tc), gogrpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
|
||||
|
||||
conn, err := gogrpc.Dial(cfg.URL, opts...)
|
||||
if err != nil {
|
||||
return nil, secure, errors.Wrap(errGrpcConnect, err)
|
||||
}
|
||||
|
||||
return conn, secure, nil
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// Package grpc contains the domain concept definitions needed to support
|
||||
// Agent Client grpc functionality.
|
||||
package grpc
|
||||
Vendored
+256
@@ -1,3 +1,259 @@
|
||||
# github.com/beorn7/perks v1.0.1
|
||||
## explicit; go 1.11
|
||||
github.com/beorn7/perks/quantile
|
||||
# github.com/caarlos0/env/v7 v7.1.0
|
||||
## explicit; go 1.17
|
||||
github.com/caarlos0/env/v7
|
||||
# github.com/cenkalti/backoff/v4 v4.2.1
|
||||
## explicit; go 1.18
|
||||
github.com/cenkalti/backoff/v4
|
||||
# github.com/cespare/xxhash/v2 v2.2.0
|
||||
## explicit; go 1.11
|
||||
github.com/cespare/xxhash/v2
|
||||
# github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519
|
||||
## explicit; go 1.15
|
||||
github.com/digitalocean/go-libvirt
|
||||
github.com/digitalocean/go-libvirt/internal/constants
|
||||
github.com/digitalocean/go-libvirt/internal/event
|
||||
github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2
|
||||
github.com/digitalocean/go-libvirt/socket
|
||||
github.com/digitalocean/go-libvirt/socket/dialers
|
||||
# github.com/felixge/httpsnoop v1.0.3
|
||||
## explicit; go 1.13
|
||||
github.com/felixge/httpsnoop
|
||||
# github.com/go-kit/kit v0.12.0
|
||||
## explicit; go 1.17
|
||||
github.com/go-kit/kit/endpoint
|
||||
github.com/go-kit/kit/metrics
|
||||
github.com/go-kit/kit/metrics/internal/lv
|
||||
github.com/go-kit/kit/metrics/prometheus
|
||||
github.com/go-kit/kit/transport
|
||||
github.com/go-kit/kit/transport/grpc
|
||||
github.com/go-kit/kit/transport/http
|
||||
# github.com/go-kit/log v0.2.1
|
||||
## explicit; go 1.17
|
||||
github.com/go-kit/log
|
||||
# github.com/go-logfmt/logfmt v0.6.0
|
||||
## explicit; go 1.17
|
||||
github.com/go-logfmt/logfmt
|
||||
# github.com/go-logr/logr v1.2.4
|
||||
## explicit; go 1.16
|
||||
github.com/go-logr/logr
|
||||
github.com/go-logr/logr/funcr
|
||||
# github.com/go-logr/stdr v1.2.2
|
||||
## explicit; go 1.16
|
||||
github.com/go-logr/stdr
|
||||
# github.com/go-zoo/bone v1.3.0
|
||||
## explicit; go 1.9
|
||||
github.com/go-zoo/bone
|
||||
# github.com/gofrs/uuid v4.4.0+incompatible
|
||||
## explicit
|
||||
github.com/gofrs/uuid
|
||||
# github.com/golang/protobuf v1.5.3
|
||||
## explicit; go 1.9
|
||||
github.com/golang/protobuf/jsonpb
|
||||
github.com/golang/protobuf/proto
|
||||
github.com/golang/protobuf/ptypes
|
||||
github.com/golang/protobuf/ptypes/any
|
||||
github.com/golang/protobuf/ptypes/duration
|
||||
github.com/golang/protobuf/ptypes/timestamp
|
||||
# github.com/mainflux/mainflux v0.0.0-20230726142711-2b78902e0170
|
||||
## explicit; go 1.20
|
||||
github.com/mainflux/mainflux
|
||||
github.com/mainflux/mainflux/logger
|
||||
github.com/mainflux/mainflux/pkg/errors
|
||||
github.com/mainflux/mainflux/pkg/uuid
|
||||
# github.com/matttproud/golang_protobuf_extensions v1.0.4
|
||||
## explicit; go 1.9
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
# github.com/prometheus/client_golang v1.16.0
|
||||
## explicit; go 1.17
|
||||
github.com/prometheus/client_golang/prometheus
|
||||
github.com/prometheus/client_golang/prometheus/internal
|
||||
github.com/prometheus/client_golang/prometheus/promhttp
|
||||
# github.com/prometheus/client_model v0.4.0
|
||||
## explicit; go 1.18
|
||||
github.com/prometheus/client_model/go
|
||||
# github.com/prometheus/common v0.44.0
|
||||
## explicit; go 1.18
|
||||
github.com/prometheus/common/expfmt
|
||||
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
|
||||
github.com/prometheus/common/model
|
||||
# github.com/prometheus/procfs v0.11.1
|
||||
## explicit; go 1.19
|
||||
github.com/prometheus/procfs
|
||||
github.com/prometheus/procfs/internal/fs
|
||||
github.com/prometheus/procfs/internal/util
|
||||
# github.com/subosito/gotenv v1.4.2
|
||||
## explicit; go 1.18
|
||||
github.com/subosito/gotenv
|
||||
# github.com/ultravioletrs/agent v0.0.0-20230905145147-a3c466449737
|
||||
## explicit; go 1.20
|
||||
github.com/ultravioletrs/agent/agent
|
||||
github.com/ultravioletrs/agent/agent/api/grpc
|
||||
github.com/ultravioletrs/agent/pkg/clients/grpc
|
||||
# go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal
|
||||
# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
|
||||
# go.opentelemetry.io/contrib/propagators/jaeger v1.17.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/contrib/propagators/jaeger
|
||||
# go.opentelemetry.io/otel v1.16.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/otel
|
||||
go.opentelemetry.io/otel/attribute
|
||||
go.opentelemetry.io/otel/baggage
|
||||
go.opentelemetry.io/otel/codes
|
||||
go.opentelemetry.io/otel/internal
|
||||
go.opentelemetry.io/otel/internal/attribute
|
||||
go.opentelemetry.io/otel/internal/baggage
|
||||
go.opentelemetry.io/otel/internal/global
|
||||
go.opentelemetry.io/otel/propagation
|
||||
go.opentelemetry.io/otel/semconv/internal
|
||||
go.opentelemetry.io/otel/semconv/internal/v2
|
||||
go.opentelemetry.io/otel/semconv/v1.12.0
|
||||
go.opentelemetry.io/otel/semconv/v1.17.0
|
||||
go.opentelemetry.io/otel/semconv/v1.17.0/httpconv
|
||||
# go.opentelemetry.io/otel/exporters/jaeger v1.16.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/otel/exporters/jaeger
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/agent
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/zipkincore
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift
|
||||
# go.opentelemetry.io/otel/metric v1.16.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/otel/metric
|
||||
go.opentelemetry.io/otel/metric/embedded
|
||||
# go.opentelemetry.io/otel/sdk v1.16.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/otel/sdk
|
||||
go.opentelemetry.io/otel/sdk/instrumentation
|
||||
go.opentelemetry.io/otel/sdk/internal
|
||||
go.opentelemetry.io/otel/sdk/internal/env
|
||||
go.opentelemetry.io/otel/sdk/resource
|
||||
go.opentelemetry.io/otel/sdk/trace
|
||||
# go.opentelemetry.io/otel/trace v1.16.0
|
||||
## explicit; go 1.19
|
||||
go.opentelemetry.io/otel/trace
|
||||
# golang.org/x/net v0.12.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/net/http/httpguts
|
||||
golang.org/x/net/http2
|
||||
golang.org/x/net/http2/hpack
|
||||
golang.org/x/net/idna
|
||||
golang.org/x/net/internal/timeseries
|
||||
golang.org/x/net/trace
|
||||
# golang.org/x/sync v0.3.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sync/errgroup
|
||||
# golang.org/x/sys v0.10.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sys/internal/unsafeheader
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
golang.org/x/sys/windows/registry
|
||||
# golang.org/x/text v0.11.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/text/secure/bidirule
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
# google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e
|
||||
## explicit; go 1.19
|
||||
google.golang.org/genproto/googleapis/rpc/status
|
||||
# google.golang.org/grpc v1.57.0
|
||||
## explicit; go 1.17
|
||||
google.golang.org/grpc
|
||||
google.golang.org/grpc/attributes
|
||||
google.golang.org/grpc/backoff
|
||||
google.golang.org/grpc/balancer
|
||||
google.golang.org/grpc/balancer/base
|
||||
google.golang.org/grpc/balancer/grpclb/state
|
||||
google.golang.org/grpc/balancer/roundrobin
|
||||
google.golang.org/grpc/binarylog/grpc_binarylog_v1
|
||||
google.golang.org/grpc/channelz
|
||||
google.golang.org/grpc/codes
|
||||
google.golang.org/grpc/connectivity
|
||||
google.golang.org/grpc/credentials
|
||||
google.golang.org/grpc/credentials/insecure
|
||||
google.golang.org/grpc/encoding
|
||||
google.golang.org/grpc/encoding/proto
|
||||
google.golang.org/grpc/grpclog
|
||||
google.golang.org/grpc/internal
|
||||
google.golang.org/grpc/internal/backoff
|
||||
google.golang.org/grpc/internal/balancer/gracefulswitch
|
||||
google.golang.org/grpc/internal/balancerload
|
||||
google.golang.org/grpc/internal/binarylog
|
||||
google.golang.org/grpc/internal/buffer
|
||||
google.golang.org/grpc/internal/channelz
|
||||
google.golang.org/grpc/internal/credentials
|
||||
google.golang.org/grpc/internal/envconfig
|
||||
google.golang.org/grpc/internal/grpclog
|
||||
google.golang.org/grpc/internal/grpcrand
|
||||
google.golang.org/grpc/internal/grpcsync
|
||||
google.golang.org/grpc/internal/grpcutil
|
||||
google.golang.org/grpc/internal/metadata
|
||||
google.golang.org/grpc/internal/pretty
|
||||
google.golang.org/grpc/internal/resolver
|
||||
google.golang.org/grpc/internal/resolver/dns
|
||||
google.golang.org/grpc/internal/resolver/passthrough
|
||||
google.golang.org/grpc/internal/resolver/unix
|
||||
google.golang.org/grpc/internal/serviceconfig
|
||||
google.golang.org/grpc/internal/status
|
||||
google.golang.org/grpc/internal/syscall
|
||||
google.golang.org/grpc/internal/transport
|
||||
google.golang.org/grpc/internal/transport/networktype
|
||||
google.golang.org/grpc/keepalive
|
||||
google.golang.org/grpc/metadata
|
||||
google.golang.org/grpc/peer
|
||||
google.golang.org/grpc/reflection
|
||||
google.golang.org/grpc/reflection/grpc_reflection_v1
|
||||
google.golang.org/grpc/reflection/grpc_reflection_v1alpha
|
||||
google.golang.org/grpc/resolver
|
||||
google.golang.org/grpc/serviceconfig
|
||||
google.golang.org/grpc/stats
|
||||
google.golang.org/grpc/status
|
||||
google.golang.org/grpc/tap
|
||||
# google.golang.org/protobuf v1.31.0
|
||||
## explicit; go 1.11
|
||||
google.golang.org/protobuf/encoding/protojson
|
||||
google.golang.org/protobuf/encoding/prototext
|
||||
google.golang.org/protobuf/encoding/protowire
|
||||
google.golang.org/protobuf/internal/descfmt
|
||||
google.golang.org/protobuf/internal/descopts
|
||||
google.golang.org/protobuf/internal/detrand
|
||||
google.golang.org/protobuf/internal/encoding/defval
|
||||
google.golang.org/protobuf/internal/encoding/json
|
||||
google.golang.org/protobuf/internal/encoding/messageset
|
||||
google.golang.org/protobuf/internal/encoding/tag
|
||||
google.golang.org/protobuf/internal/encoding/text
|
||||
google.golang.org/protobuf/internal/errors
|
||||
google.golang.org/protobuf/internal/filedesc
|
||||
google.golang.org/protobuf/internal/filetype
|
||||
google.golang.org/protobuf/internal/flags
|
||||
google.golang.org/protobuf/internal/genid
|
||||
google.golang.org/protobuf/internal/impl
|
||||
google.golang.org/protobuf/internal/order
|
||||
google.golang.org/protobuf/internal/pragma
|
||||
google.golang.org/protobuf/internal/set
|
||||
google.golang.org/protobuf/internal/strs
|
||||
google.golang.org/protobuf/internal/version
|
||||
google.golang.org/protobuf/proto
|
||||
google.golang.org/protobuf/reflect/protodesc
|
||||
google.golang.org/protobuf/reflect/protoreflect
|
||||
google.golang.org/protobuf/reflect/protoregistry
|
||||
google.golang.org/protobuf/runtime/protoiface
|
||||
google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/types/descriptorpb
|
||||
google.golang.org/protobuf/types/known/anypb
|
||||
google.golang.org/protobuf/types/known/durationpb
|
||||
google.golang.org/protobuf/types/known/timestamppb
|
||||
|
||||
# cloud.google.com/go/compute v1.20.1
|
||||
## explicit; go 1.19
|
||||
# github.com/beorn7/perks v1.0.1
|
||||
|
||||
Reference in New Issue
Block a user