Skip to content

Commit c521f1f

Browse files
authored
fix: always force stop when using Virtualization.framework (#350)
Issue #, if available: *Description of changes:* - always force stop when using Virtualization.framework, potential workaround for lima-vm/lima#1381 (needs more testing) *Testing done:* - local testing - [x] I've reviewed the guidance in CONTRIBUTING.md #### License Acceptance By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: Justin Alvarez <[email protected]>
1 parent 84c2634 commit c521f1f

File tree

4 files changed

+161
-1
lines changed

4 files changed

+161
-1
lines changed

cmd/finch/virtual_machine_stop.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ func (sva *stopVMAction) runAdapter(cmd *cobra.Command, args []string) error {
3939
if err != nil {
4040
return err
4141
}
42+
43+
if !force {
44+
if vmType, err := lima.GetVMType(sva.creator, sva.logger, limaInstanceName); err == nil {
45+
if vmType == lima.VZ {
46+
force = true
47+
}
48+
} else {
49+
return err
50+
}
51+
}
52+
4253
return sva.run(force)
4354
}
4455

cmd/finch/virtual_machine_stop_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@ func TestStopVMAction_runAdapter(t *testing.T) {
2828
name string
2929
mockSvc func(*mocks.Logger, *mocks.LimaCmdCreator, *gomock.Controller)
3030
args []string
31+
wantErr error
3132
}{
3233
{
3334
name: "should stop the instance",
3435
args: []string{},
3536
mockSvc: func(logger *mocks.Logger, creator *mocks.LimaCmdCreator, ctrl *gomock.Controller) {
37+
getVMTypeC := mocks.NewCommand(ctrl)
38+
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.VMType}}", limaInstanceName).Return(getVMTypeC)
39+
getVMTypeC.EXPECT().Output().Return([]byte("qemu"), nil)
40+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "qemu")
41+
3642
getVMStatusC := mocks.NewCommand(ctrl)
3743
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC)
3844
getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil)
@@ -43,6 +49,7 @@ func TestStopVMAction_runAdapter(t *testing.T) {
4349
command.EXPECT().CombinedOutput()
4450
logger.EXPECT().Info(gomock.Any()).AnyTimes()
4551
},
52+
wantErr: nil,
4653
},
4754
{
4855
name: "should force stop the instance",
@@ -55,6 +62,33 @@ func TestStopVMAction_runAdapter(t *testing.T) {
5562
command.EXPECT().CombinedOutput()
5663
logger.EXPECT().Info(gomock.Any()).AnyTimes()
5764
},
65+
wantErr: nil,
66+
},
67+
{
68+
name: "should stop the instance and use --force even when not specified if VMType == vz",
69+
args: []string{},
70+
mockSvc: func(logger *mocks.Logger, creator *mocks.LimaCmdCreator, ctrl *gomock.Controller) {
71+
getVMTypeC := mocks.NewCommand(ctrl)
72+
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.VMType}}", limaInstanceName).Return(getVMTypeC)
73+
getVMTypeC.EXPECT().Output().Return([]byte("vz"), nil)
74+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "vz")
75+
76+
command := mocks.NewCommand(ctrl)
77+
creator.EXPECT().CreateWithoutStdio("stop", "--force", limaInstanceName).Return(command)
78+
command.EXPECT().CombinedOutput()
79+
logger.EXPECT().Info(gomock.Any()).AnyTimes()
80+
},
81+
wantErr: nil,
82+
},
83+
{
84+
name: "get VMType returns an error",
85+
args: []string{},
86+
mockSvc: func(logger *mocks.Logger, creator *mocks.LimaCmdCreator, ctrl *gomock.Controller) {
87+
getVMTypeC := mocks.NewCommand(ctrl)
88+
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.VMType}}", limaInstanceName).Return(getVMTypeC)
89+
getVMTypeC.EXPECT().Output().Return([]byte("unknown"), errors.New("unrecognized VMType"))
90+
},
91+
wantErr: errors.New("unrecognized VMType"),
5892
},
5993
}
6094

@@ -70,7 +104,8 @@ func TestStopVMAction_runAdapter(t *testing.T) {
70104

71105
cmd := newStopVMCommand(lcc, logger)
72106
cmd.SetArgs(tc.args)
73-
assert.NoError(t, cmd.Execute())
107+
err := cmd.Execute()
108+
assert.Equal(t, tc.wantErr, err)
74109
})
75110
}
76111
}

pkg/lima/lima.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@ import (
1717
// https://github.com/lima-vm/lima/blob/fc783ec455a91d70639f9a1d7f22e9890fe6b1cd/pkg/store/instance.go#L23.
1818
type VMStatus int64
1919

20+
// VMType for Lima.
21+
// Relevant type defined in Lima upstream:
22+
// https://github.com/lima-vm/lima/blob/f0282b2dfc4b6795295fceb2c86acd1312cef436/pkg/limayaml/limayaml.go#L53-L54.
23+
type VMType string
24+
2025
// Finch CLI assumes there are only 4 VM status below. Adding more statuses will need to make changes in the caller side.
2126
const (
2227
Running VMStatus = iota
2328
Stopped
2429
Nonexistent
2530
Unknown
31+
QEMU VMType = "qemu"
32+
VZ VMType = "vz"
33+
NonexistentVMType VMType = "nonexistant"
34+
UnknownVMType VMType = "unknown"
2635
)
2736

2837
// GetVMStatus returns the Lima VM status.
@@ -37,6 +46,18 @@ func GetVMStatus(creator command.LimaCmdCreator, logger flog.Logger, instanceNam
3746
return toVMStatus(status, logger)
3847
}
3948

49+
// GetVMType returns the Lima VMType for a running instance.
50+
func GetVMType(creator command.LimaCmdCreator, logger flog.Logger, instanceName string) (VMType, error) {
51+
args := []string{"ls", "-f", "{{.VMType}}", instanceName}
52+
cmd := creator.CreateWithoutStdio(args...)
53+
out, err := cmd.Output()
54+
if err != nil {
55+
return UnknownVMType, err
56+
}
57+
status := strings.TrimSpace(string(out))
58+
return toVMType(status, logger)
59+
}
60+
4061
func toVMStatus(status string, logger flog.Logger) (VMStatus, error) {
4162
logger.Debugf("Status of virtual machine: %s", status)
4263
switch status {
@@ -50,3 +71,17 @@ func toVMStatus(status string, logger flog.Logger) (VMStatus, error) {
5071
return Unknown, errors.New("unrecognized system status")
5172
}
5273
}
74+
75+
func toVMType(vmType string, logger flog.Logger) (VMType, error) {
76+
logger.Debugf("VMType of virtual machine: %s", vmType)
77+
switch vmType {
78+
case "":
79+
return NonexistentVMType, nil
80+
case "qemu":
81+
return QEMU, nil
82+
case "vz":
83+
return VZ, nil
84+
default:
85+
return UnknownVMType, errors.New("unrecognized VMType")
86+
}
87+
}

pkg/lima/lima_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,82 @@ func TestGetVMStatus(t *testing.T) {
9292
})
9393
}
9494
}
95+
96+
func TestGetVMType(t *testing.T) {
97+
t.Parallel()
98+
99+
instanceName := "finch"
100+
mockArgs := []string{"ls", "-f", "{{.VMType}}", instanceName}
101+
testCases := []struct {
102+
name string
103+
want lima.VMType
104+
wantErr error
105+
mockSvc func(*mocks.LimaCmdCreator, *mocks.Logger, *mocks.Command)
106+
}{
107+
{
108+
name: "qemu VM",
109+
want: lima.QEMU,
110+
wantErr: nil,
111+
mockSvc: func(creator *mocks.LimaCmdCreator, logger *mocks.Logger, cmd *mocks.Command) {
112+
creator.EXPECT().CreateWithoutStdio(mockArgs).Return(cmd)
113+
cmd.EXPECT().Output().Return([]byte("qemu"), nil)
114+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "qemu")
115+
},
116+
},
117+
{
118+
name: "vz VM",
119+
want: lima.VZ,
120+
wantErr: nil,
121+
mockSvc: func(creator *mocks.LimaCmdCreator, logger *mocks.Logger, cmd *mocks.Command) {
122+
creator.EXPECT().CreateWithoutStdio(mockArgs).Return(cmd)
123+
cmd.EXPECT().Output().Return([]byte("vz"), nil)
124+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "vz")
125+
},
126+
},
127+
{
128+
name: "nonexistent VM",
129+
want: lima.NonexistentVMType,
130+
wantErr: nil,
131+
mockSvc: func(creator *mocks.LimaCmdCreator, logger *mocks.Logger, cmd *mocks.Command) {
132+
creator.EXPECT().CreateWithoutStdio(mockArgs).Return(cmd)
133+
cmd.EXPECT().Output().Return([]byte(" "), nil)
134+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "")
135+
},
136+
},
137+
{
138+
name: "unknown VM type",
139+
want: lima.UnknownVMType,
140+
wantErr: errors.New("unrecognized VMType"),
141+
mockSvc: func(creator *mocks.LimaCmdCreator, logger *mocks.Logger, cmd *mocks.Command) {
142+
creator.EXPECT().CreateWithoutStdio(mockArgs).Return(cmd)
143+
cmd.EXPECT().Output().Return([]byte("Broken "), nil)
144+
logger.EXPECT().Debugf("VMType of virtual machine: %s", "Broken")
145+
},
146+
},
147+
{
148+
name: "type command returns an error",
149+
want: lima.UnknownVMType,
150+
wantErr: errors.New("get VMType error"),
151+
mockSvc: func(creator *mocks.LimaCmdCreator, logger *mocks.Logger, cmd *mocks.Command) {
152+
creator.EXPECT().CreateWithoutStdio(mockArgs).Return(cmd)
153+
cmd.EXPECT().Output().Return([]byte("Broken "), errors.New("get VMType error"))
154+
},
155+
},
156+
}
157+
158+
for _, tc := range testCases {
159+
tc := tc
160+
t.Run(tc.name, func(t *testing.T) {
161+
t.Parallel()
162+
163+
ctrl := gomock.NewController(t)
164+
creator := mocks.NewLimaCmdCreator(ctrl)
165+
statusCmd := mocks.NewCommand(ctrl)
166+
logger := mocks.NewLogger(ctrl)
167+
tc.mockSvc(creator, logger, statusCmd)
168+
got, err := lima.GetVMType(creator, logger, instanceName)
169+
assert.Equal(t, tc.wantErr, err)
170+
assert.Equal(t, tc.want, got)
171+
})
172+
}
173+
}

0 commit comments

Comments
 (0)