Skip to content

Commit 90ea3ad

Browse files
authored
Merge pull request #244 from alexander-ding/enh/migrate-v2
Migrate system API group to library model
2 parents 1009791 + 158866f commit 90ea3ad

File tree

5 files changed

+333
-0
lines changed

5 files changed

+333
-0
lines changed

integrationtests/system_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import (
88
"strings"
99
"testing"
1010

11+
system "github.com/kubernetes-csi/csi-proxy/pkg/system"
12+
systemapi "github.com/kubernetes-csi/csi-proxy/pkg/system/api"
13+
1114
"github.com/kubernetes-csi/csi-proxy/client/api/system/v1alpha1"
1215
v1alpha1client "github.com/kubernetes-csi/csi-proxy/client/groups/system/v1alpha1"
16+
1317
"github.com/stretchr/testify/assert"
1418
"github.com/stretchr/testify/require"
1519
)
@@ -99,6 +103,85 @@ func TestServiceCommands(t *testing.T) {
99103

100104
}
101105

106+
func TestSystem(t *testing.T) {
107+
t.Run("GetBIOSSerialNumber", func(t *testing.T) {
108+
client, err := system.New(systemapi.New())
109+
require.Nil(t, err)
110+
111+
request := &system.GetBIOSSerialNumberRequest{}
112+
response, err := client.GetBIOSSerialNumber(context.TODO(), request)
113+
require.Nil(t, err)
114+
require.NotNil(t, response)
115+
116+
result, err := exec.Command("wmic", "bios", "get", "serialnumber").Output()
117+
require.Nil(t, err)
118+
119+
t.Logf("The serial number is %s", response.SerialNumber)
120+
121+
resultString := string(result)
122+
require.True(t, strings.Contains(resultString, response.SerialNumber))
123+
})
124+
125+
t.Run("GetService", func(t *testing.T) {
126+
const ServiceName = "MSiSCSI"
127+
client, err := system.New(systemapi.New())
128+
require.Nil(t, err)
129+
130+
// Make sure service is stopped
131+
_, err = runPowershellCmd(t, fmt.Sprintf(`Stop-Service -Name "%s"`, ServiceName))
132+
require.NoError(t, err)
133+
assertServiceStopped(t, ServiceName)
134+
135+
request := &system.GetServiceRequest{Name: ServiceName}
136+
response, err := client.GetService(context.TODO(), request)
137+
require.NoError(t, err)
138+
require.NotNil(t, response)
139+
140+
out, err := runPowershellCmd(t, fmt.Sprintf(`Get-Service -Name "%s" `+
141+
`| Select-Object DisplayName, Status, StartType | ConvertTo-Json`,
142+
ServiceName))
143+
require.NoError(t, err)
144+
145+
var serviceInfo = struct {
146+
DisplayName string `json:"DisplayName"`
147+
Status uint32 `json:"Status"`
148+
StartType uint32 `json:"StartType"`
149+
}{}
150+
151+
err = json.Unmarshal([]byte(out), &serviceInfo)
152+
require.NoError(t, err, "failed unmarshalling json out=%v", out)
153+
154+
assert.Equal(t, serviceInfo.Status, uint32(response.Status))
155+
assert.Equal(t, system.SERVICE_STATUS_STOPPED, response.Status)
156+
assert.Equal(t, serviceInfo.StartType, uint32(response.StartType))
157+
assert.Equal(t, serviceInfo.DisplayName, response.DisplayName)
158+
})
159+
160+
t.Run("Stop/Start Service", func(t *testing.T) {
161+
const ServiceName = "MSiSCSI"
162+
client, err := system.New(systemapi.New())
163+
require.Nil(t, err)
164+
165+
_, err = runPowershellCmd(t, fmt.Sprintf(`Stop-Service -Name "%s"`, ServiceName))
166+
require.NoError(t, err)
167+
assertServiceStopped(t, ServiceName)
168+
169+
startReq := &system.StartServiceRequest{Name: ServiceName}
170+
startResp, err := client.StartService(context.TODO(), startReq)
171+
172+
assert.NoError(t, err)
173+
assert.NotNil(t, startResp)
174+
assertServiceStarted(t, ServiceName)
175+
176+
stopReq := &system.StopServiceRequest{Name: ServiceName}
177+
stopResp, err := client.StopService(context.TODO(), stopReq)
178+
179+
assert.NoError(t, err)
180+
assert.NotNil(t, stopResp)
181+
assertServiceStopped(t, ServiceName)
182+
})
183+
}
184+
102185
func assertServiceStarted(t *testing.T, serviceName string) {
103186
assertServiceStatus(t, serviceName, "Running")
104187
}

pkg/system/api/api.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os/exec"
7+
"strings"
8+
9+
"github.com/kubernetes-csi/csi-proxy/pkg/utils"
10+
)
11+
12+
// Implements the System OS API calls. All code here should be very simple
13+
// pass-through to the OS APIs. Any logic around the APIs should go in
14+
// pkg/system/system.go so that logic can be easily unit-tested
15+
// without requiring specific OS environments.
16+
17+
type API interface {
18+
GetBIOSSerialNumber() (string, error)
19+
GetService(name string) (*ServiceInfo, error)
20+
StartService(name string) error
21+
StopService(name string, force bool) error
22+
}
23+
24+
type systemAPI struct{}
25+
26+
func New() API {
27+
return systemAPI{}
28+
}
29+
30+
func (systemAPI) GetBIOSSerialNumber() (string, error) {
31+
// Taken from Kubernetes vSphere cloud provider
32+
// https://github.com/kubernetes/kubernetes/blob/103e926604de6f79161b78af3e792d0ed282bc06/staging/src/k8s.io/legacy-cloud-providers/vsphere/vsphere_util_windows.go#L28
33+
result, err := exec.Command("wmic", "bios", "get", "serialnumber").Output()
34+
if err != nil {
35+
return "", err
36+
}
37+
lines := strings.FieldsFunc(string(result), func(r rune) bool {
38+
switch r {
39+
case '\n', '\r':
40+
return true
41+
default:
42+
return false
43+
}
44+
})
45+
if len(lines) != 2 {
46+
return "", fmt.Errorf("received unexpected value retrieving host uuid: %q", string(result))
47+
}
48+
return lines[1], nil
49+
}
50+
51+
func (systemAPI) GetService(name string) (*ServiceInfo, error) {
52+
script := `Get-Service -Name $env:ServiceName | Select-Object DisplayName, Status, StartType | ` +
53+
`ConvertTo-JSON`
54+
cmdEnv := fmt.Sprintf("ServiceName=%s", name)
55+
out, err := utils.RunPowershellCmd(script, cmdEnv)
56+
if err != nil {
57+
return nil, fmt.Errorf("error querying service name=%s. cmd: %s, output: %s, error: %v", name, script, string(out), err)
58+
}
59+
60+
var serviceInfo ServiceInfo
61+
err = json.Unmarshal(out, &serviceInfo)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
return &serviceInfo, nil
67+
}
68+
69+
func (systemAPI) StartService(name string) error {
70+
script := `Start-Service -Name $env:ServiceName`
71+
cmdEnv := fmt.Sprintf("ServiceName=%s", name)
72+
out, err := utils.RunPowershellCmd(script, cmdEnv)
73+
if err != nil {
74+
return fmt.Errorf("error starting service name=%s. cmd: %s, output: %s, error: %v", name, script, string(out), err)
75+
}
76+
77+
return nil
78+
}
79+
80+
func (systemAPI) StopService(name string, force bool) error {
81+
script := `Stop-Service -Name $env:ServiceName -Force:$([System.Convert]::ToBoolean($env:Force))`
82+
out, err := utils.RunPowershellCmd(script, fmt.Sprintf("ServiceName=%s", name), fmt.Sprintf("Force=%t", force))
83+
if err != nil {
84+
return fmt.Errorf("error stopping service name=%s. cmd: %s, output: %s, error: %v", name, script, string(out), err)
85+
}
86+
87+
return nil
88+
}

pkg/system/api/types.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package api
2+
3+
type ServiceInfo struct {
4+
// Service display name
5+
DisplayName string `json:"DisplayName"`
6+
7+
// Service start type
8+
StartType uint32 `json:"StartType"`
9+
10+
// Service status
11+
Status uint32 `json:"Status"`
12+
}

pkg/system/system.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package system
2+
3+
import (
4+
"context"
5+
6+
systemapi "github.com/kubernetes-csi/csi-proxy/pkg/system/api"
7+
"k8s.io/klog/v2"
8+
)
9+
10+
type System struct {
11+
hostAPI systemapi.API
12+
}
13+
14+
type Interface interface {
15+
GetBIOSSerialNumber(context.Context, *GetBIOSSerialNumberRequest) (*GetBIOSSerialNumberResponse, error)
16+
GetService(context.Context, *GetServiceRequest) (*GetServiceResponse, error)
17+
StartService(context.Context, *StartServiceRequest) (*StartServiceResponse, error)
18+
StopService(context.Context, *StopServiceRequest) (*StopServiceResponse, error)
19+
}
20+
21+
// check that System implements Interface
22+
var _ Interface = &System{}
23+
24+
func New(hostAPI systemapi.API) (*System, error) {
25+
return &System{
26+
hostAPI: hostAPI,
27+
}, nil
28+
}
29+
30+
func (s *System) GetBIOSSerialNumber(context context.Context, request *GetBIOSSerialNumberRequest) (*GetBIOSSerialNumberResponse, error) {
31+
klog.V(4).Infof("calling GetBIOSSerialNumber")
32+
response := &GetBIOSSerialNumberResponse{}
33+
serialNumber, err := s.hostAPI.GetBIOSSerialNumber()
34+
if err != nil {
35+
klog.Errorf("failed GetBIOSSerialNumber: %v", err)
36+
return response, err
37+
}
38+
39+
response.SerialNumber = serialNumber
40+
return response, nil
41+
}
42+
43+
func (s *System) GetService(context context.Context, request *GetServiceRequest) (*GetServiceResponse, error) {
44+
klog.V(4).Infof("calling GetService name=%s", request.Name)
45+
response := &GetServiceResponse{}
46+
info, err := s.hostAPI.GetService(request.Name)
47+
if err != nil {
48+
klog.Errorf("failed GetService: %v", err)
49+
return response, err
50+
}
51+
52+
response.DisplayName = info.DisplayName
53+
response.StartType = Startype(info.StartType)
54+
response.Status = ServiceStatus(info.Status)
55+
return response, nil
56+
}
57+
58+
func (s *System) StartService(context context.Context, request *StartServiceRequest) (*StartServiceResponse, error) {
59+
klog.V(4).Infof("calling StartService name=%s", request.Name)
60+
response := &StartServiceResponse{}
61+
err := s.hostAPI.StartService(request.Name)
62+
if err != nil {
63+
klog.Errorf("failed StartService: %v", err)
64+
return response, err
65+
}
66+
67+
return response, nil
68+
}
69+
70+
func (s *System) StopService(context context.Context, request *StopServiceRequest) (*StopServiceResponse, error) {
71+
klog.V(4).Infof("calling StopService name=%s", request.Name)
72+
response := &StopServiceResponse{}
73+
err := s.hostAPI.StopService(request.Name, request.Force)
74+
if err != nil {
75+
klog.Errorf("failed StopService: %v", err)
76+
return response, err
77+
}
78+
79+
return response, nil
80+
}

pkg/system/types.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package system
2+
3+
type GetBIOSSerialNumberRequest struct {
4+
}
5+
6+
type GetBIOSSerialNumberResponse struct {
7+
SerialNumber string
8+
}
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 dependent 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 ServiceStatus = iota
35+
SERVICE_STATUS_STOPPED
36+
SERVICE_STATUS_START_PENDING
37+
SERVICE_STATUS_STOP_PENDING
38+
SERVICE_STATUS_RUNNING
39+
SERVICE_STATUS_CONTINUE_PENDING
40+
SERVICE_STATUS_PAUSE_PENDING
41+
SERVICE_STATUS_PAUSED
42+
)
43+
44+
type Startype uint32
45+
46+
const (
47+
START_TYPE_BOOT Startype = iota
48+
START_TYPE_SYSTEM
49+
START_TYPE_AUTOMATIC
50+
START_TYPE_MANUAL
51+
START_TYPE_DISABLED
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+
// Used to control whether a service will start on boot, and if so on which
65+
// boot phase.
66+
StartType Startype
67+
68+
// Service status, e.g. stopped, running, paused
69+
Status ServiceStatus
70+
}

0 commit comments

Comments
 (0)