forked from control-center/serviced
/
utils.go
356 lines (329 loc) · 8.98 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*******************************************************************************
* Copyright (C) Zenoss, Inc. 2013, all rights reserved.
*
* This content is made available according to terms specified in
* License.zenoss under the directory where your Zenoss product is installed.
*
*******************************************************************************/
package serviced
import (
"github.com/zenoss/glog"
"github.com/zenoss/serviced/dao"
"bufio"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
)
var hostIdCmdString = "/usr/bin/hostid"
// hostId retreives the system's unique id, on linux this maps
// to /usr/bin/hostid.
func HostId() (hostid string, err error) {
cmd := exec.Command(hostIdCmdString)
stdout, err := cmd.Output()
if err != nil {
return hostid, err
}
return strings.TrimSpace(string(stdout)), err
}
// Path to meminfo file. Placed here so getMemorySize() is testable.
var meminfoFile = "/proc/meminfo"
// getMemorySize attempts to get the size of the installed RAM.
func getMemorySize() (size uint64, err error) {
file, err := os.Open(meminfoFile)
if err != nil {
return 0, err
}
defer file.Close()
reader := bufio.NewReader(file)
line, err := reader.ReadString('\n')
for err == nil {
if strings.Contains(line, "MemTotal:") {
parts := strings.Fields(line)
if len(parts) < 3 {
return 0, err
}
size, err := strconv.Atoi(parts[1])
if err != nil {
return 0, err
}
return uint64(size) * 1024, nil
}
line, err = reader.ReadString('\n')
}
return 0, err
}
// Represent a entry from the route command
type RouteEntry struct {
Destination string
Gateway string
Genmask string
Flags string
Metric int
Ref int
Use int
Iface string
}
// wrapper around the route command
func routeCmd() (routes []RouteEntry, err error) {
output, err := exec.Command("/sbin/route", "-A", "inet").Output()
if err != nil {
return routes, err
}
columnMap := make(map[string]int)
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(lines) < 2 {
return routes, fmt.Errorf("no routes found")
}
routes = make([]RouteEntry, len(lines)-2)
for lineNum, line := range lines {
line = strings.TrimSpace(line)
switch {
// skip first line
case lineNum == 0:
continue
case lineNum == 1:
for number, name := range strings.Fields(line) {
columnMap[name] = number
}
continue
default:
fields := strings.Fields(line)
metric, err := strconv.Atoi(fields[columnMap["Metric"]])
if err != nil {
return routes, err
}
ref, err := strconv.Atoi(fields[columnMap["Ref"]])
if err != nil {
return routes, err
}
use, err := strconv.Atoi(fields[columnMap["Use"]])
if err != nil {
return routes, err
}
routes[lineNum-2] = RouteEntry{
Destination: fields[columnMap["Destination"]],
Gateway: fields[columnMap["Gateway"]],
Genmask: fields[columnMap["Genmask"]],
Flags: fields[columnMap["Flags"]],
Metric: metric,
Ref: ref,
Use: use,
Iface: fields[columnMap["Iface"]],
}
}
}
return routes, err
}
// Get the IP bound to the hostname of the current host
func getIpAddr() (ip string, err error) {
output, err := exec.Command("hostname", "-i").Output()
if err != nil {
return ip, err
}
return strings.TrimSpace(string(output)), err
}
// Create a new Host struct from the running host's values. The resource pool id
// is set to the passed value.
func CurrentContextAsHost(poolId string) (host *dao.Host, err error) {
cpus := runtime.NumCPU()
memory, err := getMemorySize()
if err != nil {
return nil, err
}
host = dao.NewHost()
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
host.Name = hostname
hostid_str, err := HostId()
if err != nil {
return nil, err
}
host.IpAddr, err = getIpAddr()
if err != nil {
return host, err
}
host.Id = hostid_str
host.Cores = cpus
host.Memory = memory
routes, err := routeCmd()
if err != nil {
return nil, err
}
for _, route := range routes {
if route.Iface == "docker0" {
host.PrivateNetwork = route.Destination + "/" + route.Genmask
break
}
}
host.PoolId = poolId
return host, err
}
// Get the path to the currently running executable.
func ExecPath() (string, string, error) {
path, err := os.Readlink("/proc/self/exe")
if err != nil {
return "", "", err
}
return filepath.Dir(path), filepath.Base(path), nil
}
// DockerVersion contains the tuples that describe the version of docker
type DockerVersion struct {
Client []int
Server []int
}
// Compare two DockerVersion structs
func (a *DockerVersion) equals(b *DockerVersion) bool {
if len(a.Client) != len(b.Client) {
return false
}
for i, a_i := range a.Client {
if a_i != b.Client[i] {
return false
}
}
if len(a.Server) != len(b.Server) {
return false
}
for i, a_i := range a.Server {
if a_i != b.Server[i] {
return false
}
}
return true
}
// Get the docker version numbers from the runtime
func GetDockerVersion() (DockerVersion, error) {
cmd := exec.Command("docker", "version")
output, err := cmd.Output()
if err != nil {
return DockerVersion{}, err
}
return parseDockerVersion(string(output))
}
// parse Docker versions
func parseDockerVersion(output string) (version DockerVersion, err error) {
for _, line := range strings.Split(output, "\n") {
parts := strings.SplitN(line, ":", 2)
if len(parts) < 2 {
continue
}
if strings.HasPrefix(parts[0], "Client version") {
a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
b := strings.Split(a[0], ".")
version.Client = make([]int, len(b))
for i, v := range b {
x, err := strconv.Atoi(v)
if err != nil {
return version, err
}
version.Client[i] = x
}
}
if strings.HasPrefix(parts[0], "Server version") {
a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
b := strings.Split(a[0], ".")
version.Server = make([]int, len(b))
for i, v := range b {
x, err := strconv.Atoi(v)
if err != nil {
return version, err
}
version.Server[i] = x
}
}
}
if len(version.Client) == 0 {
return version, fmt.Errorf("No client version found")
}
if len(version.Server) == 0 {
return version, fmt.Errorf("No server version found")
}
return version, nil
}
//create a user directory and setting ownership and permission according to parameters
func CreateDirectory(path, username string, perm os.FileMode) error {
user, err := user.Lookup(username)
if err == nil {
err = os.MkdirAll(path, perm)
if err == nil || err == os.ErrExist {
uid, _ := strconv.Atoi(user.Uid)
gid, _ := strconv.Atoi(user.Gid)
err = os.Chown(path, uid, gid)
}
}
return err
}
// returns serviced home
func ServiceDHome() string {
return os.Getenv("SERVICED_HOME")
}
// This code is straight out of net/http/httputil
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// This differs from httputil.NewSingleHostReverseProxy in that it rewrites
// the path so that it does /not/ include the incoming path. e.g. request for
// "/mysvc/thing" when proxy is served from "/mysvc" means target is
// targeturl.Path + "/thing"; vs. httputil.NewSingleHostReverseProxy, in which
// it would be targeturl.Path + "/mysvc/thing".
func NewReverseProxy(path string, targeturl *url.URL) *httputil.ReverseProxy {
targetQuery := targeturl.RawQuery
director := func(r *http.Request) {
r.URL.Scheme = targeturl.Scheme
r.URL.Host = targeturl.Host
newpath := strings.TrimPrefix(r.URL.Path, path)
r.URL.Path = singleJoiningSlash(targeturl.Path, newpath)
if targetQuery == "" || r.URL.RawQuery == "" {
r.URL.RawQuery = targetQuery + r.URL.RawQuery
} else {
r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
}
}
return &httputil.ReverseProxy{Director: director}
}
// createVolumeDir() creates a directory on the running host using the user ids
// found within the specified image. For example, it can create a directory owned
// by the mysql user (as seen by the container) despite there being no mysql user
// on the host system
func createVolumeDir(hostPath, containerSpec, imageSpec, userSpec, permissionSpec string) error {
// FIXME: this relies on the underlying container to have /bin/sh that supports
// some advanced shell options. This should be rewriten so that serviced injects itself in the
// container and performs the operations using only go!
docker := exec.Command("docker", "run", "-rm",
"-v", hostPath+":/tmp",
imageSpec,
"/bin/sh", "-c",
fmt.Sprintf(`
chown %s /tmp && \
chmod %s /tmp && \
shopt -s nullglob && \
shopt -s dotglob && \
files=(/tmp/*) && \
if [ ${#files[@]} -eq 0 ]; then
cp -rp %s/* /tmp/
fi
`, userSpec, permissionSpec, containerSpec))
output, err := docker.CombinedOutput()
if err != nil {
glog.Errorf("could not create host volume: %s", string(output))
}
return err
}