Skip to content

Commit 5787bb2

Browse files
authored
add Suspend/Resume operation (#605)
* add Suspend/Resume operation * add tests
1 parent 920f592 commit 5787bb2

File tree

14 files changed

+362
-40
lines changed

14 files changed

+362
-40
lines changed

cluster/calcium/control.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ func (c *Calcium) ControlWorkload(ctx context.Context, IDs []string, typ string,
4343
startHook, err := c.doStartWorkload(ctx, workload, force)
4444
message = append(message, startHook...)
4545
return err
46+
case cluster.WorkloadSuspend:
47+
message, err = c.doSuspendWorkload(ctx, workload, force)
48+
return err
49+
case cluster.WorkloadResume:
50+
message, err = c.doResumeWorkload(ctx, workload, force)
51+
return err
4652
}
4753
return types.ErrInvaildControlType
4854
})
@@ -103,3 +109,39 @@ func (c *Calcium) doStopWorkload(ctx context.Context, workload *types.Workload,
103109
}
104110
return message, err
105111
}
112+
113+
func (c *Calcium) doSuspendWorkload(ctx context.Context, workload *types.Workload, force bool) (message []*bytes.Buffer, err error) {
114+
if workload.Hook != nil && len(workload.Hook.BeforeSuspend) > 0 {
115+
message, err = c.doHook(
116+
ctx,
117+
workload.ID, workload.User,
118+
workload.Hook.BeforeSuspend, workload.Env,
119+
workload.Hook.Force, workload.Privileged,
120+
force, workload.Engine,
121+
)
122+
if err != nil {
123+
return message, err
124+
}
125+
}
126+
127+
if err = workload.Suspend(ctx); err != nil {
128+
message = append(message, bytes.NewBufferString(err.Error()))
129+
}
130+
return message, err
131+
}
132+
133+
func (c *Calcium) doResumeWorkload(ctx context.Context, workload *types.Workload, force bool) (message []*bytes.Buffer, err error) {
134+
if err = workload.Resume(ctx); err != nil {
135+
return message, err
136+
}
137+
if workload.Hook != nil && len(workload.Hook.AfterResume) > 0 {
138+
message, err = c.doHook(
139+
ctx,
140+
workload.ID, workload.User,
141+
workload.Hook.AfterResume, workload.Env,
142+
workload.Hook.Force, workload.Privileged,
143+
force, workload.Engine,
144+
)
145+
}
146+
return message, err
147+
}

cluster/calcium/control_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,128 @@ func TestControlRestart(t *testing.T) {
178178
assert.NoError(t, r.Error)
179179
}
180180
}
181+
182+
func TestControlSuspend(t *testing.T) {
183+
c := NewTestCluster()
184+
ctx := context.Background()
185+
store := c.store.(*storemocks.Store)
186+
lock := &lockmocks.DistributedLock{}
187+
lock.On("Lock", mock.Anything).Return(ctx, nil)
188+
lock.On("Unlock", mock.Anything).Return(nil)
189+
store.On("CreateLock", mock.Anything, mock.Anything).Return(lock, nil)
190+
workload := &types.Workload{
191+
ID: "id1",
192+
Privileged: true,
193+
}
194+
engine := &enginemocks.API{}
195+
workload.Engine = engine
196+
store.On("GetWorkloads", mock.Anything, mock.Anything).Return([]*types.Workload{workload}, nil)
197+
// failed, hook true, remove always false
198+
hook := &types.Hook{
199+
BeforeSuspend: []string{"cmd1"},
200+
}
201+
workload.Hook = hook
202+
workload.Hook.Force = true
203+
engine.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return("", nil, nil, nil, types.ErrNilEngine)
204+
ch, err := c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadSuspend, false)
205+
assert.NoError(t, err)
206+
for r := range ch {
207+
assert.Error(t, r.Error)
208+
}
209+
// stop failed
210+
workload.Hook.Force = false
211+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadSuspend, false)
212+
engine.On("VirtualizationSuspend", mock.Anything, mock.Anything, mock.Anything).Return(types.ErrNilEngine).Once()
213+
assert.NoError(t, err)
214+
for r := range ch {
215+
assert.Error(t, r.Error)
216+
}
217+
engine.On("VirtualizationSuspend", mock.Anything, mock.Anything, mock.Anything).Return(nil)
218+
// stop success
219+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadSuspend, false)
220+
assert.NoError(t, err)
221+
for r := range ch {
222+
assert.NoError(t, r.Error)
223+
}
224+
}
225+
226+
func TestControlResume(t *testing.T) {
227+
c := NewTestCluster()
228+
ctx := context.Background()
229+
store := c.store.(*storemocks.Store)
230+
lock := &lockmocks.DistributedLock{}
231+
lock.On("Lock", mock.Anything).Return(ctx, nil)
232+
lock.On("Unlock", mock.Anything).Return(nil)
233+
store.On("CreateLock", mock.Anything, mock.Anything).Return(lock, nil)
234+
// failed by GetWorkloads
235+
store.On("GetWorkloads", mock.Anything, mock.Anything).Return(nil, types.ErrMockError).Once()
236+
ch, err := c.ControlWorkload(ctx, []string{"id1"}, "", true)
237+
assert.NoError(t, err)
238+
for r := range ch {
239+
assert.Error(t, r.Error)
240+
}
241+
workload := &types.Workload{
242+
ID: "id1",
243+
Privileged: true,
244+
}
245+
engine := &enginemocks.API{}
246+
workload.Engine = engine
247+
store.On("GetWorkloads", mock.Anything, mock.Anything).Return([]*types.Workload{workload}, nil)
248+
// failed by type
249+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, "", true)
250+
assert.NoError(t, err)
251+
for r := range ch {
252+
assert.Error(t, r.Error)
253+
}
254+
// failed by start
255+
engine.On("VirtualizationResume", mock.Anything, mock.Anything).Return(types.ErrNilEngine).Once()
256+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
257+
assert.NoError(t, err)
258+
for r := range ch {
259+
assert.Error(t, r.Error)
260+
}
261+
engine.On("VirtualizationResume", mock.Anything, mock.Anything).Return(nil)
262+
// failed by Execute
263+
hook := &types.Hook{
264+
AfterResume: []string{"cmd1", "cmd2"},
265+
}
266+
workload.Hook = hook
267+
workload.Hook.Force = false
268+
engine.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return("", nil, nil, nil, types.ErrNilEngine).Times(3)
269+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
270+
assert.NoError(t, err)
271+
for r := range ch {
272+
assert.NoError(t, r.Error)
273+
}
274+
// force false, get no error
275+
workload.Hook.Force = true
276+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
277+
assert.NoError(t, err)
278+
for r := range ch {
279+
assert.Error(t, r.Error)
280+
assert.Equal(t, r.WorkloadID, "id1")
281+
}
282+
data := io.NopCloser(bytes.NewBufferString("output"))
283+
engine.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return("eid", data, nil, nil, nil).Times(4)
284+
// failed by ExecExitCode
285+
engine.On("ExecExitCode", mock.Anything, mock.Anything, mock.Anything).Return(-1, types.ErrNilEngine).Once()
286+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
287+
assert.NoError(t, err)
288+
for r := range ch {
289+
assert.Error(t, r.Error)
290+
}
291+
// exitCode is not 0
292+
engine.On("ExecExitCode", mock.Anything, mock.Anything, mock.Anything).Return(-1, nil).Once()
293+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
294+
assert.NoError(t, err)
295+
for r := range ch {
296+
assert.Error(t, r.Error)
297+
}
298+
// exitCode is 0
299+
engine.On("ExecExitCode", mock.Anything, mock.Anything, mock.Anything).Return(0, nil)
300+
ch, err = c.ControlWorkload(ctx, []string{"id1"}, cluster.WorkloadResume, false)
301+
assert.NoError(t, err)
302+
for r := range ch {
303+
assert.NoError(t, r.Error)
304+
}
305+
}

cluster/cluster.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ const (
2828
WorkloadStart = "start"
2929
// WorkloadRestart for restart workload
3030
WorkloadRestart = "restart"
31+
// WorkloadSuspend for suspending workload
32+
WorkloadSuspend = "suspend"
33+
// WorkloadResume for resuming workload
34+
WorkloadResume = "resume"
3135
// WorkloadLock for lock workload
3236
WorkloadLock = "clock_%s"
3337
// PodLock for lock pod

engine/docker/container.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,16 @@ func (e *Engine) VirtualizationStop(ctx context.Context, ID string, gracefulTime
307307
return e.client.ContainerStop(ctx, ID, dockercontainer.StopOptions{Timeout: timeout})
308308
}
309309

310+
// VirtualizationSuspend suspends virtualization
311+
func (e *Engine) VirtualizationSuspend(context.Context, string) error {
312+
return nil
313+
}
314+
315+
// VirtualizationResume resumes virtualization
316+
func (e *Engine) VirtualizationResume(context.Context, string) error {
317+
return nil
318+
}
319+
310320
// VirtualizationRemove remove virtualization
311321
func (e *Engine) VirtualizationRemove(ctx context.Context, ID string, removeVolumes, force bool) error {
312322
if err := e.client.ContainerRemove(ctx, ID, dockertypes.ContainerRemoveOptions{RemoveVolumes: removeVolumes, Force: force}); err != nil {

engine/engine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type API interface {
4343
VirtualizationStart(ctx context.Context, ID string) error
4444
VirtualizationStop(ctx context.Context, ID string, gracefulTimeout time.Duration) error
4545
VirtualizationRemove(ctx context.Context, ID string, volumes, force bool) error
46+
VirtualizationSuspend(ctx context.Context, ID string) error
47+
VirtualizationResume(ctx context.Context, ID string) error
4648
VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error)
4749
VirtualizationLogs(ctx context.Context, opts *enginetypes.VirtualizationLogStreamOptions) (stdout, stderr io.ReadCloser, err error)
4850
VirtualizationAttach(ctx context.Context, ID string, stream, openStdin bool) (stdout, stderr io.ReadCloser, stdin io.WriteCloser, err error)

engine/fake/fake.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ func (f *EngineWithErr) VirtualizationStop(context.Context, string, time.Duratio
140140
return f.DefaultErr
141141
}
142142

143+
// VirtualizationSuspend .
144+
func (f *EngineWithErr) VirtualizationSuspend(context.Context, string) error {
145+
return f.DefaultErr
146+
}
147+
148+
// VirtualizationResume .
149+
func (f *EngineWithErr) VirtualizationResume(context.Context, string) error {
150+
return f.DefaultErr
151+
}
152+
143153
// VirtualizationRemove .
144154
func (f *EngineWithErr) VirtualizationRemove(context.Context, string, bool, bool) error {
145155
return f.DefaultErr

0 commit comments

Comments
 (0)