Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/test/pkg/agent/daemon/main.go
2499 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package main
6
7
import (
8
"context"
9
"encoding/json"
10
"fmt"
11
"io/fs"
12
"net/http"
13
"os"
14
"path/filepath"
15
"strings"
16
"time"
17
18
cgroups_v2 "github.com/gitpod-io/gitpod/common-go/cgroups/v2"
19
ctntcfg "github.com/gitpod-io/gitpod/content-service/api/config"
20
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
21
"github.com/gitpod-io/gitpod/test/pkg/agent/daemon/api"
22
"github.com/gitpod-io/gitpod/test/pkg/integration"
23
"github.com/google/nftables"
24
"github.com/mitchellh/go-ps"
25
"github.com/prometheus/procfs"
26
"github.com/vishvananda/netns"
27
"golang.org/x/xerrors"
28
)
29
30
func main() {
31
done := make(chan struct{})
32
go func() {
33
mux := http.NewServeMux()
34
mux.Handle("/shutdown", shugtdownHandler(done))
35
_ = http.ListenAndServe(":8080", mux)
36
}()
37
integration.ServeAgent(done, new(DaemonAgent))
38
}
39
40
func shugtdownHandler(done chan struct{}) http.HandlerFunc {
41
return func(w http.ResponseWriter, _ *http.Request) {
42
close(done)
43
w.Write([]byte("shutdown"))
44
w.WriteHeader(http.StatusOK)
45
}
46
}
47
48
type daemonConfig struct {
49
Daemon struct {
50
Content struct {
51
Storage ctntcfg.StorageConfig `json:"storage"`
52
} `json:"content"`
53
} `json:"daemon"`
54
}
55
56
// DaemonAgent provides ingteration test services from within ws-daemon
57
type DaemonAgent struct {
58
}
59
60
// CreateBucket reads the daemon's config, and creates a bucket
61
func (*DaemonAgent) CreateBucket(args *api.CreateBucketRequest, resp *api.CreateBucketResponse) error {
62
*resp = api.CreateBucketResponse{}
63
64
fc, err := os.ReadFile("/config/config.json")
65
if err != nil {
66
return err
67
}
68
var cfg daemonConfig
69
err = json.Unmarshal(fc, &cfg)
70
if err != nil {
71
return err
72
}
73
74
ac, err := storage.NewDirectAccess(&cfg.Daemon.Content.Storage)
75
if err != nil {
76
return err
77
}
78
79
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
80
defer cancel()
81
err = ac.Init(ctx, args.Owner, args.Workspace, "")
82
if err != nil {
83
return err
84
}
85
86
err = ac.EnsureExists(ctx)
87
if err != nil {
88
return err
89
}
90
91
return nil
92
}
93
94
func (*DaemonAgent) GetWorkspaceResources(args *api.GetWorkspaceResourcesRequest, resp *api.GetWorkspaceResourcesResponse) error {
95
*resp = api.GetWorkspaceResourcesResponse{}
96
97
filepath.WalkDir("/mnt/node-cgroups", func(path string, d fs.DirEntry, err error) error {
98
if strings.Contains(path, args.ContainerId) {
99
var returnErr error
100
cpu := cgroups_v2.NewCpuController(path)
101
quota, _, err := cpu.Max()
102
if err == nil {
103
resp.Found = true
104
resp.CpuQuota = int64(quota)
105
} else {
106
returnErr = err
107
}
108
109
io := cgroups_v2.NewIOController(path)
110
devices, err := io.Max()
111
if err == nil {
112
resp.FoundIOMax = true
113
resp.IOMax = devices
114
} else {
115
returnErr = err
116
}
117
118
return returnErr
119
}
120
121
return nil
122
})
123
return nil
124
}
125
126
func (*DaemonAgent) VerifyRateLimitingRule(args *api.VerifyRateLimitingRuleRequest, resp *api.VerifyRateLimitingRuleResponse) error {
127
*resp = api.VerifyRateLimitingRuleResponse{}
128
ring0Pid, err := findWorkspaceRing0Pid(args.ContainerId)
129
if err != nil {
130
return err
131
}
132
133
netns, err := netns.GetFromPid(int(ring0Pid))
134
if err != nil {
135
return fmt.Errorf("could not get handle for network namespace: %w", err)
136
}
137
138
nftconn, err := nftables.New(nftables.WithNetNSFd(int(netns)))
139
if err != nil {
140
return fmt.Errorf("could not establish netlink connection for nft: %w", err)
141
}
142
143
gitpodTable := &nftables.Table{
144
Name: "gitpod",
145
Family: nftables.TableFamilyIPv4,
146
}
147
148
// Check if drop stats counter exists.
149
counterObject, err := nftconn.GetObject(&nftables.CounterObj{
150
Table: gitpodTable,
151
Name: "ws-connection-drop-stats",
152
})
153
if err != nil {
154
return fmt.Errorf("could not get connection drop stats: %w", err)
155
}
156
_, ok := counterObject.(*nftables.CounterObj)
157
if !ok {
158
return fmt.Errorf("could not cast counter object")
159
}
160
161
// Check if set exists.
162
_, err = nftconn.GetSetByName(gitpodTable, "ws-connections")
163
if err != nil {
164
return fmt.Errorf("could not get set ws-connections: %w", err)
165
}
166
167
// Check if ratelimit chain exists.
168
chains, err := nftconn.ListChains()
169
if err != nil {
170
return fmt.Errorf("could not list chains: %w", err)
171
}
172
var found bool
173
for _, c := range chains {
174
if c.Name == "ratelimit" {
175
found = true
176
break
177
}
178
}
179
if !found {
180
return fmt.Errorf("chain ratelimit not found")
181
}
182
183
return nil
184
}
185
186
// findWorkspaceRing0Pid finds the ring0 process for a workspace container.
187
// It first looks up the container's process, then finds the ring0 process among its children.
188
func findWorkspaceRing0Pid(containerId string) (int, error) {
189
// Hack: need to use both procfs and go-ps, as the former provides a process' command line,
190
// while the latter provides the parent PID. Neither does both ¯\_(ツ)_/¯
191
pfs, err := procfs.NewFS("/proc")
192
if err != nil {
193
return 0, err
194
}
195
procs, err := ps.Processes()
196
if err != nil {
197
return 0, err
198
}
199
var containerProc ps.Process
200
for _, p := range procs {
201
if processContainsArg(pfs, p.Pid(), containerId) {
202
containerProc = p
203
break
204
}
205
}
206
if containerProc == nil {
207
return 0, xerrors.Errorf("no process found for container id %s", containerId)
208
}
209
210
// Find ring0 among the container's child processes.
211
ring0Pid, found := findRing0(pfs, procs, containerProc)
212
if !found {
213
return 0, xerrors.Errorf("no ring0 process found for container id %s", containerId)
214
}
215
return ring0Pid, nil
216
}
217
218
func processContainsArg(pfs procfs.FS, pid int, arg string) bool {
219
p, err := pfs.Proc(pid)
220
if err != nil {
221
return false
222
}
223
cmd, _ := p.CmdLine()
224
for _, c := range cmd {
225
if strings.Contains(c, arg) {
226
return true
227
}
228
}
229
return false
230
}
231
232
func findRing0(pfs procfs.FS, all []ps.Process, fromParent ps.Process) (int, bool) {
233
for _, proc := range all {
234
if proc.PPid() != fromParent.Pid() {
235
continue
236
}
237
if processContainsArg(pfs, proc.Pid(), "ring0") {
238
// We found the ring0 process.
239
return proc.Pid(), true
240
}
241
242
// Try looking for ring0 in any child processes.
243
pid, found := findRing0(pfs, all, proc)
244
if found {
245
return pid, true
246
}
247
}
248
return 0, false
249
}
250
251