This repository has been archived by the owner on Dec 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 226
/
start.go
227 lines (190 loc) · 7.07 KB
/
start.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
package operations
import (
"fmt"
"path"
"path/filepath"
"strings"
"time"
log "github.com/sirupsen/logrus"
api "github.com/weaveworks/ignite/pkg/apis/ignite"
meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/dmlegacy"
"github.com/weaveworks/ignite/pkg/logs"
"github.com/weaveworks/ignite/pkg/operations/lookup"
"github.com/weaveworks/ignite/pkg/providers"
"github.com/weaveworks/ignite/pkg/runtime"
"github.com/weaveworks/ignite/pkg/util"
apiruntime "github.com/weaveworks/libgitops/pkg/runtime"
)
// VMChannels can be used to get signals for different stages of VM lifecycle
type VMChannels struct {
SpawnFinished chan error
}
func StartVM(vm *api.VM, debug bool) error {
vmChans, err := StartVMNonBlocking(vm, debug)
if err != nil {
return err
}
if err := <-vmChans.SpawnFinished; err != nil {
return err
}
return nil
}
func StartVMNonBlocking(vm *api.VM, debug bool) (*VMChannels, error) {
// Inspect the VM container and remove it if it exists
inspectResult, _ := providers.Runtime.InspectContainer(vm.PrefixedID())
RemoveVMContainer(inspectResult)
// Make sure we always initialize all channels
vmChans := &VMChannels{
SpawnFinished: make(chan error),
}
// Setup the snapshot overlay filesystem
snapshotDevPath, err := dmlegacy.ActivateSnapshot(vm)
if err != nil {
return vmChans, err
}
kernelUID, err := lookup.KernelUIDForVM(vm, providers.Client)
if err != nil {
return vmChans, err
}
vmDir := filepath.Join(constants.VM_DIR, vm.GetUID().String())
kernelDir := filepath.Join(constants.KERNEL_DIR, kernelUID.String())
// Verify that the image containing ignite-spawn is pulled
// TODO: Integrate automatic pulling into pkg/runtime
if err := verifyPulled(vm.Spec.Sandbox.OCI); err != nil {
return vmChans, err
}
config := &runtime.ContainerConfig{
Cmd: []string{
fmt.Sprintf("--log-level=%s", logs.Logger.Level.String()),
vm.GetUID().String(),
},
Labels: map[string]string{"ignite.name": vm.GetName()},
Binds: []*runtime.Bind{
{
HostPath: vmDir,
ContainerPath: vmDir,
},
{
// Mount the metadata.json file specifically into the container, to a well-known place for ignite-spawn to access
HostPath: path.Join(vmDir, constants.METADATA),
ContainerPath: constants.IGNITE_SPAWN_VM_FILE_PATH,
},
{
// Mount the vmlinux file specifically into the container, to a well-known place for ignite-spawn to access
HostPath: path.Join(kernelDir, constants.KERNEL_FILE),
ContainerPath: constants.IGNITE_SPAWN_VMLINUX_FILE_PATH,
},
},
CapAdds: []string{
"SYS_ADMIN", // Needed to run "dmsetup remove" inside the container
"NET_ADMIN", // Needed for removing the IP from the container's interface
},
Devices: []*runtime.Bind{
runtime.BindBoth("/dev/mapper/control"), // This enables containerized Ignite to remove its own dm snapshot
runtime.BindBoth("/dev/net/tun"), // Needed for creating TAP adapters
runtime.BindBoth("/dev/kvm"), // Pass through virtualization support
runtime.BindBoth(snapshotDevPath), // The block device to boot from
},
StopTimeout: constants.STOP_TIMEOUT + constants.IGNITE_TIMEOUT,
PortBindings: vm.Spec.Network.Ports, // Add the port mappings to Docker
}
var envVars []string
for k, v := range vm.GetObjectMeta().Annotations {
if strings.HasPrefix(k, constants.IGNITE_SANDBOX_ENV_VAR) {
k := strings.TrimPrefix(k, constants.IGNITE_SANDBOX_ENV_VAR)
envVars = append(envVars, fmt.Sprintf("%s=%s", k, v))
}
}
config.EnvVars = envVars
// Add the volumes to the container devices
for _, volume := range vm.Spec.Storage.Volumes {
if volume.BlockDevice == nil {
continue // Skip all non block device volumes for now
}
config.Devices = append(config.Devices, &runtime.Bind{
HostPath: volume.BlockDevice.Path,
ContainerPath: path.Join(constants.IGNITE_SPAWN_VOLUME_DIR, volume.Name),
})
}
// Prepare the networking for the container, for the given network plugin
if err := providers.NetworkPlugin.PrepareContainerSpec(config); err != nil {
return vmChans, err
}
// If we're not debugging, remove the container post-run
if !debug {
config.AutoRemove = true
}
// Run the VM container in Docker
containerID, err := providers.Runtime.RunContainer(vm.Spec.Sandbox.OCI, config, vm.PrefixedID(), vm.GetUID().String())
if err != nil {
return vmChans, fmt.Errorf("failed to start container for VM %q: %v", vm.GetUID(), err)
}
// Set up the networking
result, err := providers.NetworkPlugin.SetupContainerNetwork(containerID, vm.Spec.Network.Ports...)
if err != nil {
return vmChans, err
}
if !logs.Quiet {
log.Infof("Networking is handled by %q", providers.NetworkPlugin.Name())
log.Infof("Started Firecracker VM %q in a container with ID %q", vm.GetUID(), containerID)
}
// Set the container ID for the VM
vm.Status.Runtime.ID = containerID
vm.Status.Runtime.Name = providers.RuntimeName
// Append non-loopback runtime IP addresses of the VM to its state
for _, addr := range result.Addresses {
if !addr.IP.IsLoopback() {
vm.Status.Network.IPAddresses = append(vm.Status.Network.IPAddresses, addr.IP)
}
}
vm.Status.Network.Plugin = providers.NetworkPluginName
// write the API object in a non-running state before we wait for spawn's network logic and firecracker
if err := providers.Client.VMs().Set(vm); err != nil {
return vmChans, err
}
// TODO: This is temporary until we have proper communication to the container
// It's best to perform any imperative changes to the VM object pointer before this go-routine starts
go waitForSpawn(vm, vmChans)
return vmChans, nil
}
// verifyPulled pulls the ignite-spawn image if it's not present
func verifyPulled(image meta.OCIImageRef) error {
if _, err := providers.Runtime.InspectImage(image); err != nil {
log.Infof("Pulling image %q...", image)
if err = providers.Runtime.PullImage(image); err != nil {
return err
}
// Verify the image was pulled
if _, err = providers.Runtime.InspectImage(image); err != nil {
return err
}
}
return nil
}
// TODO: This check for the Prometheus socket file is temporary
// until we get a proper ignite <-> ignite-spawn communication channel
func waitForSpawn(vm *api.VM, vmChans *VMChannels) {
const checkInterval = 100 * time.Millisecond
timer := time.Now()
for time.Since(timer) < constants.IGNITE_SPAWN_TIMEOUT {
time.Sleep(checkInterval)
if util.FileExists(path.Join(vm.ObjectPath(), constants.PROMETHEUS_SOCKET)) {
// Before we write the VM, we should REALLY REALLY re-fetch the API object from storage
vm, err := providers.Client.VMs().Get(vm.GetUID())
if err != nil {
vmChans.SpawnFinished <- err
}
// Set the VM's status to running
vm.Status.Running = true
// Set the start time for the VM
startTime := apiruntime.Timestamp()
vm.Status.StartTime = &startTime
// Write the state changes, send any errors through the channel
vmChans.SpawnFinished <- providers.Client.VMs().Set(vm)
return
}
}
vmChans.SpawnFinished <- fmt.Errorf("timeout waiting for ignite-spawn startup")
}