/
create.go
238 lines (210 loc) · 6.58 KB
/
create.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
// Copyright 2023 Nubificus LTD.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"net"
"os"
"os/exec"
"syscall"
"github.com/creack/pty"
"github.com/nubificus/urunc/pkg/unikontainers"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
)
var createUsage = `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`
var createDescription = `
The create command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "` + specConfig + `" and a root
filesystem.`
var createCommand = cli.Command{
Name: "create",
Usage: "create a container",
ArgsUsage: createUsage,
Description: createDescription,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bundle, b",
Value: "",
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "console-socket",
Value: "",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.BoolFlag{
Name: "reexec",
},
},
Action: func(context *cli.Context) error {
// FIXME: Remove or change level of log
logrus.WithField("args", os.Args).Info("urunc INVOKED")
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
err := handleNonBimaContainer(context)
if err != nil {
return err
}
if !context.Bool("reexec") {
return createUnikontainer(context)
}
return reexecUnikontainer(context)
},
}
// createUnikontainer creates a Unikernel struct from bundle data, initializes it's base dir and state.json,
// setups terminal if required and spawns reexec process,
// waits for reexec process to notify, executes CreateRuntime hooks,
// sends ACK to reexec process and executes CreateContainer hooks
func createUnikontainer(context *cli.Context) error {
containerID := context.Args().First()
rootDir := context.GlobalString("root")
if rootDir == "" {
rootDir = "/run/urunc"
}
// in the case of urunc create, the bundle is either given or the current directory
// FIXME: remove the next 2 lines from create and add to commands that require it for CRI
// ctrNamespace := filepath.Base(rootDir)
// bundlePath := filepath.Join("/run/containerd/io.containerd.runtime.v2.task/", ctrNamespace, containerID)
bundlePath := context.String("bundle")
// new unikernel from bundle
unikontainer, err := unikontainers.New(bundlePath, containerID, rootDir)
if err != nil {
return err
}
err = unikontainer.InitialSetup()
if err != nil {
return err
}
// create reexec process
selfBinary, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to retrieve urunc executable: %w", err)
}
myArgs := os.Args[1:]
myArgs = append(myArgs, "--reexec")
reexecCommand := &exec.Cmd{
Path: selfBinary,
Args: append([]string{selfBinary}, myArgs...),
SysProcAttr: &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNET,
},
Env: os.Environ(),
}
// setup terminal if required and start reexec process
if unikontainer.Spec.Process.Terminal {
ptm, err := pty.Start(reexecCommand)
if err != nil {
logrus.WithError(err).Fatal("failed to create pty")
}
defer ptm.Close()
consoleSocket := context.String("console-socket")
conn, err := net.Dial("unix", consoleSocket)
if err != nil {
logrus.WithError(err).Fatal("failed to dial console socket")
}
defer conn.Close()
uc, ok := conn.(*net.UnixConn)
if !ok {
logrus.Fatal("failed to cast unix socket")
}
defer uc.Close()
// Send file descriptor over socket.
oob := unix.UnixRights(int(ptm.Fd()))
_, _, err = uc.WriteMsgUnix([]byte(ptm.Name()), oob, nil)
if err != nil {
logrus.WithError(err).Fatal("failed to send PTY file descriptor over socket")
}
} else {
reexecCommand.Stdin = os.Stdin
reexecCommand.Stdout = os.Stdout
reexecCommand.Stderr = os.Stderr
if err := reexecCommand.Start(); err != nil {
logrus.WithError(err).Fatal("failed to start reexec process")
}
}
// Wait for reexec process to notify us
err = unikontainer.AwaitReexecStarted()
if err != nil {
return err
}
// Retrieve reexec cmd's pid and write to file and state
pid := reexecCommand.Process.Pid
err = unikontainer.Create(pid)
if err != nil {
return err
}
// execute CreateRuntime hooks
err = unikontainer.ExecuteHooks("CreateRuntime")
if err != nil {
return fmt.Errorf("failed to execute CreateRuntime hooks: %w", err)
}
// send ACK to reexec process
err = unikontainer.SendAckReexec()
if err != nil {
return fmt.Errorf("failed to send ACK to reexec process: %w", err)
}
// execute CreateRuntime hooks
err = unikontainer.ExecuteHooks("CreateContainer")
if err != nil {
return fmt.Errorf("failed to execute CreateRuntime hooks: %w", err)
}
return nil
}
// reexecUnikontainer gets a Unikernel struct from state.json,
// sends ReexecStarted message to init.sock,
// waits AckReexec message on urunc.sock,
// waits StartExecve message on urunc.sock,
// executes Prestart hooks and finally execve's the unikernel vmm.
func reexecUnikontainer(context *cli.Context) error {
containerID := context.Args().First()
rootDir := context.GlobalString("root")
if rootDir == "" {
rootDir = "/run/urunc"
}
// get Unikontainer data from state.json
unikontainer, err := unikontainers.Get(containerID, rootDir)
if err != nil {
return err
}
// send ReexecStarted message to init.sock to parent process
err = unikontainer.SendReexecStarted()
if err != nil {
return err
}
// wait AckReexec message on urunc.sock from parent process
err = unikontainer.AwaitAckReexec()
if err != nil {
return err
}
// wait StartExecve message on urunc.sock from urunc start process
err = unikontainer.AwaitStartExecve()
if err != nil {
return err
}
// execute Prestart hooks
err = unikontainer.ExecuteHooks("Prestart")
if err != nil {
return err
}
// execve
return unikontainer.Exec()
}