Skip to content

Commit fcfdb01

Browse files
authored
Merge pull request #97 from jmpfar/service-commands
Add service commands to system api group
2 parents 6966676 + e68936e commit fcfdb01

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)