Skip to content

Commit 42ce438

Browse files
authored
initial qmp && local volume hotplug (nanovms#1509)
* qmp for local volume hotplug * . * .
1 parent cecc103 commit 42ce438

10 files changed

+243
-29
lines changed

cmd/cmd_volume_test.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package cmd_test
22

33
import (
4+
"os"
45
"testing"
6+
"time"
7+
8+
"github.com/nanovms/ops/testutils"
9+
"github.com/nanovms/ops/types"
510

611
"github.com/nanovms/ops/cmd"
712
"github.com/stretchr/testify/assert"
@@ -38,35 +43,75 @@ func TestDeleteVolumeCommand(t *testing.T) {
3843
}
3944

4045
func TestAttachVolumeCommand(t *testing.T) {
41-
imageName := buildImage("test")
46+
imageName := buildWaitImage("test")
4247
defer removeImage(imageName)
43-
instanceName := buildInstance(imageName)
48+
49+
configFileName := "test-" + testutils.String(5) + ".json"
50+
expected := &types.Config{
51+
RunConfig: types.RunConfig{
52+
QMP: true,
53+
},
54+
}
55+
56+
writeConfigToFile(expected, configFileName)
57+
defer os.Remove(configFileName)
58+
59+
instanceName := buildInstanceWithConfig(imageName, configFileName)
4460
defer removeInstance(instanceName)
61+
4562
volumeName := buildVolume("vol-test")
4663
defer removeVolume(volumeName)
4764

4865
attachVolumeCmd := cmd.VolumeCommands()
4966

5067
attachVolumeCmd.SetArgs([]string{"attach", instanceName, volumeName, "does not matter"})
5168

69+
// FIXME: tests prob. should not be spawning
70+
time.Sleep(1 * time.Second)
71+
5272
err := attachVolumeCmd.Execute()
5373

5474
assert.Nil(t, err)
5575
}
5676

5777
func TestDetachVolumeCommand(t *testing.T) {
58-
imageName := buildImage("test")
78+
imageName := buildWaitImage("test")
5979
defer removeImage(imageName)
60-
instanceName := buildInstance(imageName)
80+
81+
configFileName := "test-" + testutils.String(5) + ".json"
82+
expected := &types.Config{
83+
RunConfig: types.RunConfig{
84+
QMP: true,
85+
},
86+
}
87+
88+
writeConfigToFile(expected, configFileName)
89+
defer os.Remove(configFileName)
90+
91+
instanceName := buildInstanceWithConfig(imageName, configFileName)
6192
defer removeInstance(instanceName)
93+
6294
volumeName := buildVolume("vol-test")
6395
defer removeVolume(volumeName)
6496

97+
attachVolumeCmd := cmd.VolumeCommands()
98+
99+
attachVolumeCmd.SetArgs([]string{"attach", instanceName, volumeName, "does not matter"})
100+
101+
// FIXME: tests prob. should not be spawning
102+
time.Sleep(1 * time.Second)
103+
104+
err := attachVolumeCmd.Execute()
105+
106+
assert.Nil(t, err)
107+
108+
//
109+
65110
detachVolumeCmd := cmd.VolumeCommands()
66111

67112
detachVolumeCmd.SetArgs([]string{"detach", instanceName, volumeName})
68113

69-
err := detachVolumeCmd.Execute()
114+
err = detachVolumeCmd.Execute()
70115

71116
assert.Nil(t, err)
72117
}

cmd/utils_test.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package cmd_test
33
import (
44
"encoding/json"
55
"os"
6+
"strconv"
67
"testing"
8+
"time"
79

810
"github.com/nanovms/ops/testutils"
911
"github.com/nanovms/ops/types"
@@ -49,8 +51,24 @@ func buildImage(imageName string) string {
4951
return imageName
5052
}
5153

54+
func buildWaitImage(imageName string) string {
55+
imageName += testutils.String(5)
56+
basicWaitProgram := testutils.BuildWaitProgram()
57+
defer os.Remove(basicWaitProgram)
58+
59+
createImageCmd := cmd.ImageCommands()
60+
61+
createImageCmd.SetArgs([]string{"create", basicWaitProgram, "-i", imageName})
62+
63+
createImageCmd.Execute()
64+
65+
return imageName
66+
}
67+
5268
func buildInstance(imageName string) string {
53-
instanceName := imageName + testutils.String(5)
69+
tm := strconv.FormatInt(time.Now().Unix(), 10)
70+
instanceName := imageName + "-" + tm
71+
5472
createInstanceCmd := cmd.InstanceCommands()
5573

5674
createInstanceCmd.SetArgs([]string{"create", imageName, "--instance-name", instanceName})
@@ -63,6 +81,22 @@ func buildInstance(imageName string) string {
6381
return instanceName
6482
}
6583

84+
func buildInstanceWithConfig(imageName string, config string) string {
85+
tm := strconv.FormatInt(time.Now().Unix(), 10)
86+
instanceName := imageName + "-" + tm
87+
88+
createInstanceCmd := cmd.InstanceCommands()
89+
90+
createInstanceCmd.SetArgs([]string{"create", imageName, "--instance-name", instanceName, "-c", config})
91+
92+
err := createInstanceCmd.Execute()
93+
if err != nil {
94+
log.Error(err)
95+
}
96+
97+
return instanceName
98+
}
99+
66100
func removeInstance(instanceName string) {
67101
createInstanceCmd := cmd.InstanceCommands()
68102

provider/onprem/onprem_volume.go

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package onprem
22

33
import (
44
"fmt"
5+
"net"
56
"os"
67
"path"
78
"strconv"
@@ -65,17 +66,112 @@ func (op *OnPrem) DeleteVolume(ctx *lepton.Context, name string) error {
6566
// on `ops image create --mount`, it simply creates a mount path
6667
// with the given volume label
6768
// label can refer to volume UUID or volume label
69+
//
70+
// You must start the instance with QMP otherwise this won't work.
71+
// {
72+
// "RunConfig": {
73+
// "QMP": true
74+
// }
75+
// }
6876
func (op *OnPrem) AttachVolume(ctx *lepton.Context, image, name string, attachID int) error {
69-
log.Warn("not implemented")
70-
fmt.Println("use <ops run> or <ops pkg load> with --mounts flag instead")
71-
fmt.Println("alternatively, use <ops image create -t onprem> with --mounts flag")
72-
fmt.Println("and run it with <ops instance create -t onprem>")
77+
vol := ""
78+
79+
buildDir := ctx.Config().VolumesDir
80+
vols, err := GetVolumes(buildDir, nil)
81+
if err != nil {
82+
fmt.Println(err)
83+
}
84+
85+
for i := 0; i < len(vols); i++ {
86+
if vols[i].Name == name {
87+
vol = vols[i].Path
88+
}
89+
}
90+
91+
last := getMgmtPort(image)
92+
93+
commands := []string{
94+
`{ "execute": "qmp_capabilities" }`,
95+
`{ "execute": "blockdev-add", "arguments": {"driver": "raw", "node-name":"` + name + `", "file": {"driver": "file", "filename": "` + vol + `"} } }`,
96+
`{ "execute": "device_add", "arguments": {"driver": "scsi-hd", "bus": "scsi0.0", "drive": "` + name + `", "id": "` + name + `"}}`,
97+
}
98+
99+
c, err := net.Dial("tcp", "localhost:"+last)
100+
if err != nil {
101+
fmt.Println(err)
102+
}
103+
defer c.Close()
104+
105+
for i := 0; i < len(commands); i++ {
106+
_, err := c.Write([]byte(commands[i] + "\n"))
107+
if err != nil {
108+
fmt.Println(err)
109+
}
110+
received := make([]byte, 1024)
111+
_, err = c.Read(received)
112+
if err != nil {
113+
fmt.Println(err)
114+
os.Exit(1)
115+
}
116+
}
73117
return nil
74118
}
75119

120+
// set to 4 + last 4 of instanceid
121+
// we don't want 0 and needs to be less than 65k
122+
// could still have collisions regardless; punting
123+
func getMgmtPort(image string) string {
124+
ts := strings.Split(image, "-")[1]
125+
return "4" + ts[len(ts)-4:]
126+
}
127+
76128
// DetachVolume detaches volume
77129
func (op *OnPrem) DetachVolume(ctx *lepton.Context, image, name string) error {
78-
log.Warn("not implemented")
130+
vol := ""
131+
132+
buildDir := ctx.Config().VolumesDir
133+
vols, err := GetVolumes(buildDir, nil)
134+
if err != nil {
135+
fmt.Println(err)
136+
}
137+
138+
for i := 0; i < len(vols); i++ {
139+
if vols[i].Name == name {
140+
vol = vols[i].Path
141+
}
142+
}
143+
144+
if vol != "" {
145+
fmt.Printf("removing %s\n", vol)
146+
}
147+
148+
last := getMgmtPort(image)
149+
150+
commands := []string{
151+
`{ "execute": "qmp_capabilities" }`,
152+
`{ "execute": "device_del", "arguments": {"id": "` + name + `"}}`,
153+
`{ "execute": "blockdev-del", "arguments": {"node-name":"` + name + `" } }`,
154+
}
155+
156+
c, err := net.Dial("tcp", "localhost:"+last)
157+
if err != nil {
158+
fmt.Println(err)
159+
}
160+
defer c.Close()
161+
162+
for i := 0; i < len(commands); i++ {
163+
_, err := c.Write([]byte(commands[i] + "\n"))
164+
if err != nil {
165+
fmt.Println(err)
166+
}
167+
received := make([]byte, 1024)
168+
_, err = c.Read(received)
169+
if err != nil {
170+
println("Read data failed:", err.Error())
171+
os.Exit(1)
172+
}
173+
}
174+
79175
return nil
80176
}
81177

provider/upcloud/upcloud_image.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (p *Provider) CreateImage(ctx *lepton.Context, imagePath string) error {
5757
return err
5858
}
5959

60-
ctx.Logger().Debug("%+v", templateDetails)
60+
ctx.Logger().Debugf("%+v", templateDetails)
6161

6262
err = p.waitForStorageState(storageDetails.UUID, "online")
6363
if err != nil {
@@ -115,7 +115,7 @@ func (p *Provider) GetImages(ctx *lepton.Context) (images []lepton.CloudImage, e
115115
return
116116
}
117117

118-
ctx.Logger().Debug("%+v", templates)
118+
ctx.Logger().Debugf("%+v", templates)
119119

120120
for _, s := range templates.Storages {
121121
images = append(images, *p.parseStorageToCloudImage(&s))

provider/upcloud/upcloud_instance.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ func (p *Provider) CreateInstance(ctx *lepton.Context) error {
5555
return err
5656
}
5757

58-
ctx.Logger().Debug("%+v", serverDetails)
58+
ctx.Logger().Debugf("%+v", serverDetails)
5959

6060
ctx.Logger().Info("getting ops tags")
6161
opsTag, err := p.findOrCreateTag(opsTag)
6262
if err != nil {
63-
ctx.Logger().Warn("failed creating ops tag: %s", err)
63+
ctx.Logger().Warnf("failed creating ops tag: %s", err)
6464
return nil
6565
}
6666

@@ -69,7 +69,7 @@ func (p *Provider) CreateInstance(ctx *lepton.Context) error {
6969
Description: "Creted with image " + image.Name,
7070
})
7171
if err != nil {
72-
ctx.Logger().Warn("failed creating image tag: %s", err)
72+
ctx.Logger().Warnf("failed creating image tag: %s", err)
7373
return nil
7474
}
7575

@@ -82,7 +82,7 @@ func (p *Provider) CreateInstance(ctx *lepton.Context) error {
8282

8383
_, err = p.upcloud.TagServer(context.Background(), assignOpsTagsRequest)
8484
if err != nil {
85-
ctx.Logger().Warn("failed assigning ops tags: %s", err)
85+
ctx.Logger().Warnf("failed assigning ops tags: %s", err)
8686
return nil
8787
}
8888

@@ -133,7 +133,7 @@ func (p *Provider) GetInstances(ctx *lepton.Context) (instances []lepton.CloudIn
133133

134134
opsTag, err := p.findOrCreateTag(opsTag)
135135
if err != nil {
136-
ctx.Logger().Warn("failed creating tags: %s", err)
136+
ctx.Logger().Warnf("failed creating tags: %s", err)
137137

138138
var servers *upcloud.Servers
139139
servers, err = p.upcloud.GetServers(context.Background())
@@ -191,7 +191,7 @@ func (p *Provider) DeleteInstance(ctx *lepton.Context, instancename string) (err
191191
if instance.Status != "stopped" {
192192
err = p.stopServer(instance.ID)
193193
if err != nil {
194-
ctx.Logger().Warn("failed stopping server: %s", err)
194+
ctx.Logger().Warnf("failed stopping server: %s", err)
195195
}
196196

197197
err = p.waitForServerState(instance.ID, "stopped")
@@ -204,7 +204,7 @@ func (p *Provider) DeleteInstance(ctx *lepton.Context, instancename string) (err
204204
UUID: instance.ID,
205205
}
206206

207-
ctx.Logger().Debug(`deleting server with uuid "%s"`, instance.ID)
207+
ctx.Logger().Debugf(`deleting server with uuid "%s"`, instance.ID)
208208
err = p.upcloud.DeleteServer(context.Background(), deleteServerReq)
209209

210210
return
@@ -217,7 +217,7 @@ func (p *Provider) StopInstance(ctx *lepton.Context, instancename string) (err e
217217
return
218218
}
219219

220-
ctx.Logger().Debug(`stopping server with uuid "%s"`, instance.ID)
220+
ctx.Logger().Debugf(`stopping server with uuid "%s"`, instance.ID)
221221
err = p.stopServer(instance.ID)
222222

223223
return
@@ -240,7 +240,7 @@ func (p *Provider) StartInstance(ctx *lepton.Context, instancename string) (err
240240
return
241241
}
242242

243-
ctx.Logger().Debug(`starting server with uuid "%s"`, instance.ID)
243+
ctx.Logger().Debugf(`starting server with uuid "%s"`, instance.ID)
244244

245245
err = p.startServer(instance.ID)
246246

@@ -259,7 +259,7 @@ func (p *Provider) startServer(uuid string) (err error) {
259259

260260
// GetInstanceByName returns upcloud instance with given name
261261
func (p *Provider) GetInstanceByName(ctx *lepton.Context, name string) (instance *lepton.CloudInstance, err error) {
262-
ctx.Logger().Debug(`getting instance by name "%s"`, name)
262+
ctx.Logger().Debugf(`getting instance by name "%s"`, name)
263263
server, err := p.getServerByName(ctx, name)
264264
if err != nil {
265265
return

0 commit comments

Comments
 (0)