From acaa04bc87936897ab95a141ff0a9cb31763bc13 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Sun, 23 Apr 2023 13:04:51 -0500 Subject: [PATCH] Add Restful service add restful service to VFKit which defaults to `tcp://localhost:8081`. The URL can be changed using the newly introduced --restful-uri option. The schemes may be `tcp` or `unix` (unix domain socket). The service for the unix domain implementation is incomplete and should be considered a TODO still. users can also opt out of the service with a `--restful-uri none`. the restful service now replies to three endpoints: /vm/state GET which returns the state of the vm /vm/state POST which allows for a state change /vm/inspect GET which returns basic information about the virtual machine error handling for the endpoints needs to be completed still. these endpoints should return http response codes (like 200) based on the error handling. this can be completed in a subsequent PR. /version GET should also probably be added. I was unsure if we should return the version of vfkit (my first inclination) or some sort of API version. maybe we should return both? added tests for new content where applicable Signed-off-by: Brent Baude --- cmd/vfkit/main.go | 31 +++++++- cmd/vfkit/root.go | 5 +- doc/usage.md | 34 +++++++++ go.mod | 30 ++++++-- go.sum | 93 +++++++++++++++++++++--- pkg/cmdline/cmdline.go | 29 +++++++- pkg/cmdline/cmdline_test.go | 64 +++++++++++++++++ pkg/rest/config.go | 9 +++ pkg/rest/define/config.go | 32 +++++++++ pkg/rest/rest.go | 138 ++++++++++++++++++++++++++++++++++++ pkg/rest/rest_test.go | 130 +++++++++++++++++++++++++++++++++ pkg/rest/state_change.go | 42 +++++++++++ pkg/rest/vm_config.go | 15 ++++ pkg/util/strings.go | 12 ++++ pkg/util/strings_test.go | 94 ++++++++++++++++++++++++ 15 files changed, 737 insertions(+), 21 deletions(-) create mode 100644 pkg/cmdline/cmdline_test.go create mode 100644 pkg/rest/config.go create mode 100644 pkg/rest/define/config.go create mode 100644 pkg/rest/rest.go create mode 100644 pkg/rest/rest_test.go create mode 100644 pkg/rest/state_change.go create mode 100644 pkg/rest/vm_config.go create mode 100644 pkg/util/strings_test.go diff --git a/cmd/vfkit/main.go b/cmd/vfkit/main.go index 5fe4e128..602ce4f8 100644 --- a/cmd/vfkit/main.go +++ b/cmd/vfkit/main.go @@ -1,3 +1,4 @@ +//go:build darwin // +build darwin /* @@ -29,6 +30,7 @@ import ( "github.com/Code-Hex/vz/v3" "github.com/crc-org/vfkit/pkg/cmdline" "github.com/crc-org/vfkit/pkg/config" + "github.com/crc-org/vfkit/pkg/rest" "github.com/crc-org/vfkit/pkg/vf" "github.com/docker/go-units" log "github.com/sirupsen/logrus" @@ -110,18 +112,41 @@ func waitForVMState(vm *vz.VirtualMachine, state vz.VirtualMachineState) error { } } -func runVirtualMachine(vmConfig *config.VirtualMachine) error { - vzVMConfig, err := vf.ToVzVirtualMachineConfig(vmConfig) +func runVFKit(vmConfig *config.VirtualMachine, opts *cmdline.Options) error { + uri, err := rest.ParseRestfulURI(opts.RestfulURI) if err != nil { return err } + scheme, err := rest.ToRestScheme(uri.Scheme) + if err != nil { + return err + } + + vzVMConfig, err := vf.ToVzVirtualMachineConfig(vmConfig) + if err != nil { + return err + } vm, err := vz.NewVirtualMachine(vzVMConfig) if err != nil { return err } - err = vm.Start() + // Do not enable the rests server if user sets scheme to None + if scheme != rest.None { + go func() { + // start the restful service + srv := rest.NewServer(rest.NewVzVirtualMachine(vm, vzVMConfig), scheme, uri) + if err := srv.Start(); err != nil { + log.Error(err) + } + }() + } + return runVirtualMachine(vmConfig, vm) +} + +func runVirtualMachine(vmConfig *config.VirtualMachine, vm *vz.VirtualMachine) error { + err := vm.Start() if err != nil { return err } diff --git a/cmd/vfkit/root.go b/cmd/vfkit/root.go index 55f6093a..ab98ea3e 100644 --- a/cmd/vfkit/root.go +++ b/cmd/vfkit/root.go @@ -26,11 +26,14 @@ var rootCmd = &cobra.Command{ } logrus.SetLevel(ll) } + if err := cmdline.ValidateInput(opts); err != nil { + return err + } vmConfig, err := newVMConfiguration(opts) if err != nil { return err } - return runVirtualMachine(vmConfig) + return runVFKit(vmConfig, opts) }, Version: vfkitVersion, } diff --git a/doc/usage.md b/doc/usage.md index 311dd99f..de09a0a6 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -13,6 +13,12 @@ Device configuration is optional, but most VM will need a disk image and a netwo Set the log-level for VFKit. Supported values are `debug`, `info`, and `error`. +- `--restful-URI` + +The URI (address) of the restful service. The default is `tcp://localhost:8081`. Valid schemes are +`tcp`, `none`, or `unix`. In the case of unix, the "host" portion would be a path to where the unix domain +socket will be stored. A scheme of `none` disables the restful service. + ### Virtual Machine Resources These options specify the amount of RAM and the number of CPUs which will be available to the virtual machine. @@ -208,3 +214,31 @@ The share can be mounted in the guest with `mount -t virtio-fs vfkitTag /mnt`, w #### Example `--device virtio-fs,sharedDir=/Users/virtuser/vfkit/,mountTag=vfkit-share` + +## Restful Service + +### Get VM state + +Used to obtain the state of the virtual machine that is being run by VFKit. + +GET `/vm/state` +Response: {"state": "string"} + +### Change VM State + +Change the state of the virtual machine. Valid states are: +* Hardstop +* Pause +* Resume +* Stop + +POST `/vm/state` {"new_state": "new value"} + +Response: http 200 + +### Inspect VM + +Get description of the virtual machine + +GET `/vm/inspect` +Response: { "cpus": uint, "memory": uint64, "devices": []config.VirtIODevice } \ No newline at end of file diff --git a/go.mod b/go.mod index 5336fccf..6df48504 100644 --- a/go.mod +++ b/go.mod @@ -5,22 +5,42 @@ go 1.17 require ( github.com/Code-Hex/vz/v3 v3.0.4 github.com/docker/go-units v0.4.0 + github.com/gin-gonic/gin v1.9.0 github.com/h2non/filetype v1.1.3 github.com/prashantgupta24/mac-sleep-notifier v1.0.1 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 - golang.org/x/sys v0.3.0 + golang.org/x/sys v0.5.0 inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 ) require ( + github.com/bytedance/sonic v1.8.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/crypto v0.5.0 // indirect golang.org/x/mod v0.9.0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e35e5769..78f4fd2d 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,100 @@ github.com/Code-Hex/vz/v3 v3.0.4 h1:oB74JSjSdIijjHkoYWG5ws7bHpx69m1w3PopFQdjFSc= github.com/Code-Hex/vz/v3 v3.0.4/go.mod h1:+xPQOXVzNaZ4OeIIhlJFumtbFhsvRfqKwX7wuZS4dFA= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/prashantgupta24/mac-sleep-notifier v1.0.1 h1:xd1lPtnn1gxGNjD2tCoVDoOtiQcQ8B9KNFhcWgGqreQ= github.com/prashantgupta24/mac-sleep-notifier v1.0.1/go.mod h1:bcfTio1xW+rjjZzdF0kbMEs9mcCEmrOBOSK+Jeml7zM= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= @@ -49,6 +103,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL 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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -57,25 +114,41 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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= inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0 h1:PqdHrvQRVK1zapJkd0qf6+tevvSIcWdfenVqJd3PHWU= inet.af/tcpproxy v0.0.0-20220326234310-be3ee21c9fa0/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/cmdline/cmdline.go b/pkg/cmdline/cmdline.go index ce1e3bd1..64215c0e 100644 --- a/pkg/cmdline/cmdline.go +++ b/pkg/cmdline/cmdline.go @@ -1,6 +1,9 @@ package cmdline -import "github.com/spf13/cobra" +import ( + "github.com/crc-org/vfkit/pkg/rest" + "github.com/spf13/cobra" +) type Options struct { Vcpus uint @@ -16,9 +19,13 @@ type Options struct { Devices []string + RestfulURI string + LogLevel string } +const DefaultRestfulURI = "none://" + func AddFlags(cmd *cobra.Command, opts *Options) { cmd.Flags().StringVarP(&opts.VmlinuzPath, "kernel", "k", "", "path to the virtual machine linux kernel") cmd.Flags().StringVarP(&opts.KernelCmdline, "kernel-cmdline", "C", "", "linux kernel command line") @@ -36,8 +43,26 @@ func AddFlags(cmd *cobra.Command, opts *Options) { cmd.Flags().UintVarP(&opts.MemoryMiB, "memory", "m", 512, "virtual machine RAM size in mibibytes") cmd.Flags().StringVarP(&opts.TimeSync, "timesync", "t", "", "sync guest time when host wakes up from sleep") - cmd.Flags().StringArrayVarP(&opts.Devices, "device", "d", []string{}, "devices") cmd.Flags().StringVar(&opts.LogLevel, "log-level", "", "set log level") + cmd.Flags().StringVar(&opts.RestfulURI, "restful-uri", DefaultRestfulURI, "URI address for RestFul services") + +} + +func ValidateInput(opts *Options) error { + if err := validateRestfulURI(opts.RestfulURI); err != nil { + return err + } + // more parsing of opts can be done here + return nil +} + +func validateRestfulURI(inputURI string) error { + if inputURI != DefaultRestfulURI { + if _, err := rest.ParseRestfulURI(inputURI); err != nil { + return err + } + } + return nil } diff --git a/pkg/cmdline/cmdline_test.go b/pkg/cmdline/cmdline_test.go new file mode 100644 index 00000000..ca8f3ef2 --- /dev/null +++ b/pkg/cmdline/cmdline_test.go @@ -0,0 +1,64 @@ +package cmdline + +import "testing" + +func Test_validateRestfulURI(t *testing.T) { + type args struct { + inputURI string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "valid tcp", + args: args{ + inputURI: "tcp://localhost:8080", + }, + wantErr: false, + }, + { + name: "invalid tcp - no host", + args: args{ + inputURI: "tcp://", + }, + wantErr: true, + }, + { + name: "invalid scheme", + args: args{ + inputURI: "http://localhost", + }, + wantErr: true, + }, + { + name: "valid uds", + args: args{ + inputURI: "unix:///my/socket/goes/here/vfkit.sock", + }, + wantErr: false, + }, + { + name: "invalid uds - no host", + args: args{ + inputURI: "unix://", + }, + wantErr: true, + }, + { + name: "none", + args: args{ + inputURI: "none://", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateRestfulURI(tt.args.inputURI); (err != nil) != tt.wantErr { + t.Errorf("validateRestfulURI() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/rest/config.go b/pkg/rest/config.go new file mode 100644 index 00000000..6f92856a --- /dev/null +++ b/pkg/rest/config.go @@ -0,0 +1,9 @@ +package rest + +type ServiceScheme int + +const ( + Tcp ServiceScheme = iota + Unix + None +) diff --git a/pkg/rest/define/config.go b/pkg/rest/define/config.go new file mode 100644 index 00000000..35191057 --- /dev/null +++ b/pkg/rest/define/config.go @@ -0,0 +1,32 @@ +package define + +// InspectResponse is used when responding to a request for +// information about the virtual machine +type InspectResponse struct { + CPUs uint `json:"cpus"` + Memory uint64 `json:"memory"` + //Devices []config.VirtioDevice `json:"devices"` +} + +// StateResponse is for responding to a request for virtual +// machine state +type StateResponse struct { + State string `json:"state"` +} + +// StateChangeRequest is used by the restful service consumer +// to ask for a virtual machine state change +type StateChangeRequest struct { + NewState StateChange `json:"new_state"` +} + +// StateChange is a string strong typing of values for changing +// the state of a virtual machine +type StateChange string + +const ( + Resume StateChange = "Resume" + Pause StateChange = "Pause" + Stop StateChange = "Stop" + HardStop StateChange = "HardStop" +) diff --git a/pkg/rest/rest.go b/pkg/rest/rest.go new file mode 100644 index 00000000..f77449e8 --- /dev/null +++ b/pkg/rest/rest.go @@ -0,0 +1,138 @@ +package rest + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/crc-org/vfkit/pkg/rest/define" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// VFKitService is used for the restful service; it describes +// the variables of the service like host/path but also has +// the router object +type VFKitService struct { + Host string + Path string + Port int + router *gin.Engine + Scheme ServiceScheme +} + +// Start initiates the already configured restful service +func (v *VFKitService) Start() error { + logrus.Debugf("starting rest service on %s", v.Host) + return http.ListenAndServe(v.Host, v.router) +} + +// NewServer creates a new restful service +func NewServer(vm *VzVirtualMachine, scheme ServiceScheme, uri *url.URL) *VFKitService { + r := gin.Default() + s := VFKitService{ + router: r, + Host: uri.Host, + Scheme: scheme, + } + + // Handlers for the restful service. This is where endpoints are defined. + r.GET("/vm/state", vm.getVMState) + r.POST("/vm/state", vm.setVMState) + r.GET("/vm/inspect", vm.inspect) + return &s +} + +// inspect returns information about the virtual machine like hw resources +// and devices +func (vm *VzVirtualMachine) inspect(c *gin.Context) { + //return func(w http.ResponseWriter, r *http.Request) { + ii := define.InspectResponse{ + // TODO complete me + CPUs: 1, + Memory: 2048, + //Devices: vm.Devices, + } + c.JSON(http.StatusOK, ii) +} + +// getVMState retrieves the current vm state +func (vm *VzVirtualMachine) getVMState(c *gin.Context) { + current := vm.GetState() + c.JSON(http.StatusOK, gin.H{"state": current.String()}) +} + +// setVMState requests a state change on a virtual machine. At this time only +// the following states are valid: +// Pause - pause a running machine +// Resume - resume a paused machine +// Stop - stops a running machine +// HardStop - forceably stops a running machine +func (vm *VzVirtualMachine) setVMState(c *gin.Context) { + var ( + response error + s define.StateChangeRequest + ) + + if err := c.ShouldBindJSON(&s); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + switch s.NewState { + case define.Pause: + response = vm.Pause() + case define.Resume: + response = vm.Resume() + case define.Stop: + response = vm.Stop() + case define.HardStop: + response = vm.HardStop() + default: + eMsg := fmt.Errorf("invalid new StateResponse: %s", s.NewState) + logrus.Error(eMsg) + c.JSON(http.StatusBadRequest, gin.H{"error": eMsg.Error()}) + return + + } + if response != nil { + logrus.Errorf("failed action %s: %q", s.NewState, response) + c.JSON(http.StatusInternalServerError, gin.H{"error": response.Error()}) + return + } + c.Status(http.StatusAccepted) +} + +// ParseRestfulURI validates the input URI and returns an URL object +func ParseRestfulURI(inputURI string) (*url.URL, error) { + restURI, err := url.ParseRequestURI(inputURI) + if err != nil { + return nil, err + } + scheme, err := ToRestScheme(restURI.Scheme) + if err != nil { + return nil, err + } + if scheme == Tcp && len(restURI.Host) < 1 { + return nil, errors.New("invalid uri host: none provided") + } + if scheme == Unix && len(restURI.Path) < 1 { + return nil, errors.New("invalid uri path: none provided") + } + return restURI, err +} + +// ToRestScheme converts a string to a ServiceScheme +func ToRestScheme(s string) (ServiceScheme, error) { + switch strings.ToUpper(s) { + case "NONE": + return None, nil + case "UNIX": + return Unix, nil + case "TCP": + return Tcp, nil + } + return None, fmt.Errorf("invalid scheme %s", s) +} diff --git a/pkg/rest/rest_test.go b/pkg/rest/rest_test.go new file mode 100644 index 00000000..647c553f --- /dev/null +++ b/pkg/rest/rest_test.go @@ -0,0 +1,130 @@ +package rest + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "net/url" + "testing" +) + +func TestParseRestfulURI(t *testing.T) { + type args struct { + inputURI string + } + tests := []struct { + name string + args args + want *url.URL + wantErr assert.ErrorAssertionFunc + }{ + { + name: "valid tcp", + args: args{ + inputURI: "tcp://localhost:8080", + }, + want: &url.URL{ + Scheme: "tcp", + Host: "localhost:8080", + }, + wantErr: assert.NoError, + }, + { + name: "valid unix", + args: args{ + inputURI: "unix:///var/tmp/socket.sock", + }, + want: &url.URL{ + Scheme: "unix", + Path: "/var/tmp/socket.sock", + }, + wantErr: assert.NoError, + }, + { + name: "tcp - no host information", + args: args{ + inputURI: "tcp://", + }, + want: nil, + wantErr: assert.Error, + }, + { + name: "unix - no path", + args: args{ + inputURI: "unix://", + }, + want: nil, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseRestfulURI(tt.args.inputURI) + if !tt.wantErr(t, err, fmt.Sprintf("ParseRestfulURI(%v)", tt.args.inputURI)) { + return + } + assert.Equalf(t, tt.want, got, "ParseRestfulURI(%v)", tt.args.inputURI) + }) + } +} + +func TestToRestScheme(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want ServiceScheme + wantErr assert.ErrorAssertionFunc + }{ + { + name: "valid none", + args: args{ + s: "none", + }, + want: None, + wantErr: assert.NoError, + }, + { + name: "valid unix", + args: args{ + s: "unix", + }, + want: Unix, + wantErr: assert.NoError, + }, + { + name: "valid tcp", + args: args{ + s: "tcp", + }, + want: Tcp, + wantErr: assert.NoError, + }, + { + name: "invalid input", + args: args{ + s: "foobar", + }, + want: 2, + wantErr: assert.Error, + }, + { + name: "case doesnt matter", + args: args{ + s: "UnIx", + }, + want: Unix, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ToRestScheme(tt.args.s) + if !tt.wantErr(t, err, fmt.Sprintf("ToRestScheme(%v)", tt.args.s)) { + return + } + assert.Equalf(t, tt.want, got, "ToRestScheme(%v)", tt.args.s) + }) + } +} diff --git a/pkg/rest/state_change.go b/pkg/rest/state_change.go new file mode 100644 index 00000000..636d03cc --- /dev/null +++ b/pkg/rest/state_change.go @@ -0,0 +1,42 @@ +package rest + +import ( + "errors" + + "github.com/Code-Hex/vz/v3" + "github.com/crc-org/vfkit/pkg/rest/define" + "github.com/sirupsen/logrus" +) + +// ErrNotImplemented Temporary Error Message +var ErrNotImplemented = errors.New("function not implemented yet") + +// ChangeState execute a state change (i.e. running to stopped) +func (vm *VzVirtualMachine) ChangeState(newState define.StateChange) error { + return ErrNotImplemented +} + +// GetState returns state of the VM +func (vm *VzVirtualMachine) GetState() vz.VirtualMachineState { + return vm.VzVM.State() +} + +func (vm *VzVirtualMachine) Pause() error { + logrus.Debug("pausing virtual machine") + return vm.VzVM.Pause() +} + +func (vm *VzVirtualMachine) Resume() error { + logrus.Debug("resuming machine") + return vm.VzVM.Resume() +} + +func (vm *VzVirtualMachine) Stop() error { + logrus.Debug("stopping machine") + _, err := vm.VzVM.RequestStop() + return err +} +func (vm *VzVirtualMachine) HardStop() error { + logrus.Debug("force stopping machine") + return vm.VzVM.Stop() +} diff --git a/pkg/rest/vm_config.go b/pkg/rest/vm_config.go new file mode 100644 index 00000000..a808b7d7 --- /dev/null +++ b/pkg/rest/vm_config.go @@ -0,0 +1,15 @@ +package rest + +import ( + "github.com/Code-Hex/vz/v3" +) + +type VzVirtualMachine struct { + VzVM *vz.VirtualMachine + state vz.VirtualMachineState + config *vz.VirtualMachineConfiguration +} + +func NewVzVirtualMachine(vm *vz.VirtualMachine, config *vz.VirtualMachineConfiguration) *VzVirtualMachine { + return &VzVirtualMachine{config: config, VzVM: vm} +} diff --git a/pkg/util/strings.go b/pkg/util/strings.go index ddcc02c9..a74fbfd3 100644 --- a/pkg/util/strings.go +++ b/pkg/util/strings.go @@ -9,3 +9,15 @@ func TrimQuotes(str string) string { return str } + +func StringInSlice(st string, sl []string) bool { + if sl == nil { + return false + } + for _, s := range sl { + if st == s { + return true + } + } + return false +} diff --git a/pkg/util/strings_test.go b/pkg/util/strings_test.go new file mode 100644 index 00000000..343925b3 --- /dev/null +++ b/pkg/util/strings_test.go @@ -0,0 +1,94 @@ +package util + +import "testing" + +func TestStringInSlice(t *testing.T) { + type args struct { + st string + sl []string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "valid find", + args: args{ + st: "apple", + sl: []string{"apple", "banana", "orange"}, + }, + want: true, + }, + { + name: "invalid find", + args: args{ + st: "grape", + sl: []string{"apple", "banana", "orange"}, + }, + want: false, + }, + { + name: "slice is nil", + args: args{ + st: "grape", + sl: nil, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := StringInSlice(tt.args.st, tt.args.sl); got != tt.want { + t.Errorf("StringInSlice() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrimQuotes(t *testing.T) { + type args struct { + str string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "valid", + args: args{ + str: "\"foobar\"", + }, + want: "foobar", + }, + { + name: "only front quote", + args: args{ + str: "\"foobar", + }, + want: "\"foobar", + }, + { + name: "only end quote", + args: args{ + str: "foobar\"", + }, + want: "foobar\"", + }, + { + name: "no quote", + args: args{ + str: "foobar", + }, + want: "foobar", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TrimQuotes(tt.args.str); got != tt.want { + t.Errorf("TrimQuotes() = %v, want %v", got, tt.want) + } + }) + } +}