Skip to content

Commit ea935da

Browse files
committed
Add service commands to system api group
Adding methods that can start and stop services as well as query some general service information. Adding the methods to the still unreleased v1alpha1 Change-Id: I7fc8b15712fe37095ccf246b847042659d963be4
1 parent 1e4843a commit ea935da

File tree

13 files changed

+1585
-54
lines changed

13 files changed

+1585
-54
lines changed

client/api/system/v1alpha1/api.pb.go

Lines changed: 500 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/api/system/v1alpha1/api.proto

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/system/v1alp
77
service System {
88
// GetBIOSSerialNumber returns the device's serial number
99
rpc GetBIOSSerialNumber(GetBIOSSerialNumberRequest) returns (GetBIOSSerialNumberResponse) {}
10+
11+
// StartService starts a Windows service
12+
rpc StartService(StartServiceRequest) returns (StartServiceResponse) {}
13+
14+
// StopService stops a Windows service
15+
rpc StopService(StopServiceRequest) returns (StopServiceResponse) {}
16+
17+
// GetService queries a Windows service state
18+
rpc GetService(GetServiceRequest) returns (GetServiceResponse) {}
1019
}
1120

1221
message GetBIOSSerialNumberRequest {
@@ -17,3 +26,62 @@ message GetBIOSSerialNumberResponse {
1726
// Serial number
1827
string serial_number = 1;
1928
}
29+
30+
message StartServiceRequest {
31+
// Service name (as listed in System\CCS\Services keys)
32+
string name = 1;
33+
}
34+
35+
message StartServiceResponse {
36+
// Intentionally empty
37+
}
38+
39+
message StopServiceRequest {
40+
// Service name (as listed in System\CCS\Services keys)
41+
string name = 1;
42+
43+
// Forces stopping of services that has dependant services
44+
bool force = 2;
45+
}
46+
47+
message StopServiceResponse {
48+
// Intentionally empty
49+
}
50+
51+
52+
// https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status#members
53+
enum ServiceStatus {
54+
SERVICE_STATUS_UNKNOWN = 0;
55+
SERVICE_STATUS_STOPPED = 1;
56+
SERVICE_STATUS_START_PENDING = 2;
57+
SERVICE_STATUS_STOP_PENDING = 3;
58+
SERVICE_STATUS_RUNNING = 4;
59+
SERVICE_STATUS_CONTINUE_PENDING = 5;
60+
SERVICE_STATUS_PAUSE_PENDING = 6;
61+
SERVICE_STATUS_PAUSED = 7;
62+
}
63+
64+
// https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-changeserviceconfiga
65+
enum StartType {
66+
START_TYPE_BOOT = 0;
67+
START_TYPE_SYSTEM = 1;
68+
START_TYPE_AUTOMATIC = 2;
69+
START_TYPE_MANUAL = 3;
70+
START_TYPE_DISABLED = 4;
71+
}
72+
73+
message GetServiceRequest {
74+
// Service name (as listed in System\CCS\Services keys)
75+
string name = 1;
76+
}
77+
78+
message GetServiceResponse {
79+
// Service display name
80+
string display_name = 1;
81+
82+
// Service start type
83+
StartType start_type = 2;
84+
85+
// Service status
86+
ServiceStatus status = 3;
87+
}

client/groups/system/v1alpha1/client_generated.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integrationtests/system_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package integrationtests
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
57
"os/exec"
68
"strings"
79
"testing"
810

911
"github.com/kubernetes-csi/csi-proxy/client/api/system/v1alpha1"
1012
v1alpha1client "github.com/kubernetes-csi/csi-proxy/client/groups/system/v1alpha1"
13+
"github.com/stretchr/testify/assert"
1114
"github.com/stretchr/testify/require"
1215
)
1316

@@ -31,3 +34,79 @@ func TestGetBIOSSerialNumber(t *testing.T) {
3134
require.True(t, strings.Contains(resultString, response.SerialNumber))
3235
})
3336
}
37+
38+
func TestServiceCommands(t *testing.T) {
39+
t.Run("GetService", func(t *testing.T) {
40+
const ServiceName = "MSiSCSI"
41+
client, err := v1alpha1client.NewClient()
42+
require.Nil(t, err)
43+
defer client.Close()
44+
45+
request := &v1alpha1.GetServiceRequest{Name: ServiceName}
46+
response, err := client.GetService(context.TODO(), request)
47+
require.NoError(t, err)
48+
require.NotNil(t, response)
49+
50+
out, err := runPowershellCmd(fmt.Sprintf(`Get-Service -Name "%s" `+
51+
`| Select-Object DisplayName, Status, StartType | ConvertTo-Json`,
52+
ServiceName))
53+
require.NoError(t, err)
54+
55+
var serviceInfo = struct {
56+
DisplayName string `json:"DisplayName"`
57+
Status uint32 `json:"Status"`
58+
StartType uint32 `json:"StartType"`
59+
}{}
60+
61+
err = json.Unmarshal([]byte(out), &serviceInfo)
62+
require.NoError(t, err, "failed unmarshalling json out=%v", out)
63+
64+
assert.Equal(t, serviceInfo.Status, uint32(response.Status))
65+
assert.Equal(t, serviceInfo.StartType, uint32(response.StartType))
66+
assert.Equal(t, serviceInfo.DisplayName, response.DisplayName)
67+
})
68+
69+
t.Run("Stop/Start Service", func(t *testing.T) {
70+
const ServiceName = "MSiSCSI"
71+
client, err := v1alpha1client.NewClient()
72+
require.Nil(t, err)
73+
defer client.Close()
74+
75+
_, err = runPowershellCmd(`Stop-Service -Name "MSiSCSI"`)
76+
require.NoError(t, err)
77+
assertServiceStopped(t, ServiceName)
78+
79+
startReq := &v1alpha1.StartServiceRequest{Name: ServiceName}
80+
startResp, err := client.StartService(context.TODO(), startReq)
81+
82+
assert.NoError(t, err)
83+
assert.NotNil(t, startResp)
84+
assertServiceStarted(t, ServiceName)
85+
86+
stopReq := &v1alpha1.StopServiceRequest{Name: ServiceName}
87+
stopResp, err := client.StopService(context.TODO(), stopReq)
88+
89+
assert.NoError(t, err)
90+
assert.NotNil(t, stopResp)
91+
assertServiceStopped(t, ServiceName)
92+
})
93+
94+
}
95+
96+
func assertServiceStarted(t *testing.T, serviceName string) {
97+
assertServiceStatus(t, serviceName, "Running")
98+
}
99+
100+
func assertServiceStopped(t *testing.T, serviceName string) {
101+
assertServiceStatus(t, serviceName, "Stopped")
102+
}
103+
104+
func assertServiceStatus(t *testing.T, serviceName string, status string) {
105+
out, err := runPowershellCmd(fmt.Sprintf(`Get-Service -Name "%s" | `+
106+
`Select-Object -ExpandProperty Status`, serviceName))
107+
if !assert.NoError(t, err, "Failed getting service out=%s", out) {
108+
return
109+
}
110+
111+
assert.Equal(t, strings.TrimSpace(out), status)
112+
}

internal/os/system/api.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package system
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"os"
57
"os/exec"
68
"strings"
79
)
@@ -11,6 +13,17 @@ import (
1113
// internal/server/system/server.go so that logic can be easily unit-tested
1214
// without requiring specific OS environments.
1315

16+
type ServiceInfo struct {
17+
// Service display name
18+
DisplayName string `json:"DisplayName"`
19+
20+
// Service start type
21+
StartType uint32 `json:"StartType"`
22+
23+
// Service status
24+
Status uint32 `json:"Status"`
25+
}
26+
1427
type APIImplementor struct{}
1528

1629
func New() APIImplementor {
@@ -37,3 +50,51 @@ func (APIImplementor) GetBIOSSerialNumber() (string, error) {
3750
}
3851
return lines[1], nil
3952
}
53+
54+
func (APIImplementor) GetService(name string) (*ServiceInfo, error) {
55+
script := `Get-Service -Name $env:ServiceName | Select-Object DisplayName, Status, StartType | ` +
56+
`ConvertTo-JSON`
57+
cmd := exec.Command("powershell", "/c", script)
58+
cmd.Env = append(os.Environ(), fmt.Sprintf("ServiceName=%s", name))
59+
60+
out, err := cmd.CombinedOutput()
61+
if err != nil {
62+
return nil, fmt.Errorf("error querying service name=%s. cmd: %s, output: %s, error: %v", name, cmd, string(out), err)
63+
}
64+
65+
var serviceInfo ServiceInfo
66+
err = json.Unmarshal(out, &serviceInfo)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return &serviceInfo, nil
72+
}
73+
74+
func (APIImplementor) StartService(name string) error {
75+
script := `Start-Service -Name $env:ServiceName`
76+
cmd := exec.Command("powershell", "/c", script)
77+
cmd.Env = append(os.Environ(), fmt.Sprintf("ServiceName=%s", name))
78+
79+
out, err := cmd.CombinedOutput()
80+
if err != nil {
81+
return fmt.Errorf("error starting service name=%s. cmd: %s, output: %s, error: %v", name, cmd, string(out), err)
82+
}
83+
84+
return nil
85+
}
86+
87+
func (APIImplementor) StopService(name string, force bool) error {
88+
script := `Stop-Service -Name $env:ServiceName -Force:$([System.Convert]::ToBoolean($env:Force))`
89+
cmd := exec.Command("powershell", "/c", script)
90+
cmd.Env = append(os.Environ(),
91+
fmt.Sprintf("ServiceName=%s", name),
92+
fmt.Sprintf("Force=%t", force))
93+
94+
out, err := cmd.CombinedOutput()
95+
if err != nil {
96+
return fmt.Errorf("error stopping service name=%s. cmd: %s, output: %s, error: %v", name, cmd, string(out), err)
97+
}
98+
99+
return nil
100+
}

internal/server/system/internal/types.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,63 @@ type GetBIOSSerialNumberRequest struct {
66
type GetBIOSSerialNumberResponse struct {
77
SerialNumber string
88
}
9+
10+
type StartServiceRequest struct {
11+
// Service name (as listed in System\CCS\Services keys)
12+
Name string
13+
}
14+
15+
type StartServiceResponse struct {
16+
// Intentionally empty
17+
}
18+
19+
type StopServiceRequest struct {
20+
// Service name (as listed in System\CCS\Services keys)
21+
Name string
22+
23+
// Forces stopping of services that has dependant services
24+
Force bool
25+
}
26+
27+
type StopServiceResponse struct {
28+
// Intentionally empty
29+
}
30+
31+
type ServiceStatus uint32
32+
33+
const (
34+
SERVICE_STATUS_UNKNOWN = 0
35+
SERVICE_STATUS_STOPPED = 1
36+
SERVICE_STATUS_START_PENDING = 2
37+
SERVICE_STATUS_STOP_PENDING = 3
38+
SERVICE_STATUS_RUNNING = 4
39+
SERVICE_STATUS_CONTINUE_PENDING = 5
40+
SERVICE_STATUS_PAUSE_PENDING = 6
41+
SERVICE_STATUS_PAUSED = 7
42+
)
43+
44+
type Startype uint32
45+
46+
const (
47+
START_TYPE_BOOT = 0
48+
START_TYPE_SYSTEM = 1
49+
START_TYPE_AUTOMATIC = 2
50+
START_TYPE_MANUAL = 3
51+
START_TYPE_DISABLED = 4
52+
)
53+
54+
type GetServiceRequest struct {
55+
// Service name (as listed in System\CCS\Services keys)
56+
Name string
57+
}
58+
59+
type GetServiceResponse struct {
60+
// Service display name
61+
DisplayName string
62+
63+
// Service start type
64+
StartType Startype
65+
66+
// Service status
67+
Status ServiceStatus
68+
}

internal/server/system/internal/types_generated.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)