Skip to content

Commit 305312e

Browse files
committed
refactor: Break Exec and create in smaller reusable functions
Break the large Exec function and the create function into smaller functions that cna be reused: - split createUnikontainer into newUnikontainer (bundle parse and InitialSetup) and the reexec handshake - make SetupNet a standalone function - pull buildUnikernelCommand and execMonitor out of Exec The rationale is to let the later port of libcontianer to use some of this functionality directly instead of duplicating logic. Signed-off-by: Charalampos Mainas <charalampos.mainas@gmail.com>
1 parent 088f905 commit 305312e

2 files changed

Lines changed: 65 additions & 46 deletions

File tree

cmd/urunc/create.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,15 @@ var createCommand = &cli.Command{
8282
},
8383
}
8484

85-
// createUnikontainer creates a Unikernel struct from bundle data,
86-
// initializes it's base dir and state.json,
87-
// setups terminal if required and spawns reexec process,
88-
// waits for reexec process to notify, executes CreateRuntime hooks,
89-
// sends ACK to reexec process
90-
func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (err error) {
91-
err = nil
85+
// newUnikontainer parses the bundle and performs the host-side preparation for
86+
// the monitor execution environment (Unikontainer, base directory, state and
87+
// monitor resources). It never returns for a container that is not a urunc
88+
// container: those are handed over to the real runc with an execve.
89+
func newUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (*unikontainers.Unikontainer, error) {
9290
containerID := cmd.Args().First()
93-
if err = validateID(containerID); err != nil {
94-
return err
91+
err := validateID(containerID)
92+
if err != nil {
93+
return nil, err
9594
}
9695
metrics.SetLoggerContainerID(containerID)
9796
metrics.Capture(m.TS00)
@@ -105,7 +104,7 @@ func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (
105104
if bundlePath == "" {
106105
bundlePath, err = os.Getwd()
107106
if err != nil {
108-
return err
107+
return nil, err
109108
}
110109
}
111110

@@ -114,21 +113,33 @@ func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (
114113
if err != nil {
115114
if errors.Is(err, unikontainers.ErrQueueProxy) ||
116115
errors.Is(err, unikontainers.ErrNotUnikernel) {
117-
// Exec runc to handle non unikernel containers
118-
err = runcExec()
119-
return err
116+
// Exec runc to handle non urunc containers.
117+
// It should never return.
118+
return nil, runcExec()
120119
}
121-
return err
120+
return nil, err
122121
}
123122
metrics.Capture(m.TS01)
124123

125124
err = unikontainer.InitialSetup()
126125
if err != nil {
127-
return err
126+
return nil, err
128127
}
129-
130128
metrics.Capture(m.TS02)
131129

130+
return unikontainer, nil
131+
}
132+
133+
// createUnikontainer creates a Unikernel struct from bundle data, initializes
134+
// it's base dir and state.json, setups terminal if required and spawns reexec
135+
// process, waits for reexec process to notify, executes CreateRuntime hooks,
136+
// sends ACK to reexec process
137+
func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (err error) {
138+
unikontainer, err := newUnikontainer(cmd, uruncCfg)
139+
if err != nil {
140+
return err
141+
}
142+
132143
// Create socket for nsenter
133144
initSockParent, initSockChild, err := newSockPair("init")
134145
if err != nil {

pkg/unikontainers/unikontainers.go

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,17 @@ func (u *Unikontainer) SetRunningState() error {
245245
return u.saveContainerState()
246246
}
247247

248-
func (u *Unikontainer) SetupNet() (types.NetDevParams, error) {
249-
networkType := u.getNetworkType()
248+
// SetupNet creates the sandbox's network device (tap) in the current network
249+
// namespace and returns its parameters; uid and gid own the tap device.
250+
func SetupNet(networkType string, uid, gid uint32) (types.NetDevParams, error) {
250251
uniklog.WithField("network type", networkType).Debug("Retrieved network type")
251252
netArgs := types.NetDevParams{}
252253
netManager, err := network.NewNetworkManager(networkType)
253254
if err != nil {
254255
return netArgs, fmt.Errorf("failed to create network manager for %s type: %v", networkType, err)
255256
}
256257

257-
networkInfo, err := netManager.NetworkSetup(u.Spec.Process.User.UID, u.Spec.Process.User.GID)
258+
networkInfo, err := netManager.NetworkSetup(uid, gid)
258259
if err != nil {
259260
// TODO: Handle this case better. We do not need to show an error
260261
// since there was no network in the container. Therefore, we
@@ -443,9 +444,6 @@ func monitorMemoryBytes(defaultMem uint, resources *specs.LinuxResources) uint64
443444
return mem
444445
}
445446

446-
// buildMonitorSpec assembles the base MonitorSpec: everything the monitor needs
447-
// that can be derived from the OCI spec, the container's annotations and the
448-
// monitor resources gathered during InitialSetup.
449447
func (u *Unikontainer) buildMonitorSpec(rootfsParams types.RootfsParams, monRes monitorResources) types.MonitorSpec {
450448
var mSpec types.MonitorSpec
451449

@@ -596,7 +594,7 @@ func (u *Unikontainer) Exec(metrics m.Writer) error {
596594
}
597595

598596
// handle network
599-
netArgs, err := u.SetupNet()
597+
netArgs, err := SetupNet(u.getNetworkType(), u.Spec.Process.User.UID, u.Spec.Process.User.GID)
600598
if err != nil {
601599
uniklog.Errorf("failed to setup network: %v", err)
602600
return err
@@ -646,20 +644,11 @@ func (u *Unikontainer) Exec(metrics m.Writer) error {
646644
}
647645

648646
// unikernel
649-
err = unikernel.Init(unikernelParams)
650-
if errors.Is(err, unikernels.ErrUndefinedVersion) ||
651-
errors.Is(err, unikernels.ErrVersionParsing) {
652-
uniklog.WithError(err).Error("an error occurred while initializing the unikernel")
653-
} else if err != nil {
654-
return err
655-
}
656-
657647
// build the unikernel command
658-
unikernelCmd, err := unikernel.CommandString()
648+
vmmArgs.Command, err = buildUnikernelCommand(unikernel, unikernelParams)
659649
if err != nil {
660650
return err
661651
}
662-
vmmArgs.Command = unikernelCmd
663652

664653
// pivot
665654
_, err = findNS(u.Spec.Linux.Namespaces, specs.MountNamespace)
@@ -697,38 +686,57 @@ func (u *Unikontainer) Exec(metrics m.Writer) error {
697686
return err
698687
}
699688

700-
uniklog.Debug("calling vmm execve")
701-
metrics.Capture(m.TS18)
702-
703-
// Build the VMM command once and verify it can be constructed successfully.
704-
// This ensures we don't report the container as started if command building fails.
689+
// Build the VMM command once and verify it can be constructed successfully, so
690+
// we do not report the container as started if command building fails.
705691
execCmd, err := vmm.BuildExecCmd(vmmArgs, unikernel)
706692
if err != nil {
707693
uniklog.WithError(err).Error("failed to build VMM command")
708694
return err
709695
}
710696

711-
// Notify urunc start that the monitor is ready to execute.
712-
// We send this after BuildExecCmd succeeds to avoid reporting a container
713-
// as started when the VMM command cannot be built.
714-
// TODO: The container can still be reported as running if the PreExec step
715-
// (e.g., BPF/seccomp filter setup) fails after this point. We should find
716-
// a way to handle that case as well.
697+
// Notify urunc start that the monitor is ready to execute, only after the
698+
// command builds so a container is never reported started when it cannot be.
717699
err = u.SendMessage(StartSuccess)
718700
if err != nil {
719701
return err
720702
}
721703

704+
return execMonitor(metrics, vmm, vmmArgs, execCmd)
705+
}
706+
707+
// buildUnikernelCommand initializes the unikernel with the collected parameters
708+
// and returns its command line.
709+
func buildUnikernelCommand(unikernel types.Unikernel, params types.UnikernelParams) (string, error) {
710+
err := unikernel.Init(params)
711+
if errors.Is(err, unikernels.ErrUndefinedVersion) ||
712+
errors.Is(err, unikernels.ErrVersionParsing) {
713+
uniklog.WithError(err).Error("an error occurred while initializing the unikernel")
714+
} else if err != nil {
715+
return "", err
716+
}
717+
718+
return unikernel.CommandString()
719+
}
720+
721+
// execMonitor runs the monitor's pre-exec setup and finally execve's the monitor.
722+
// It does not return on success:
723+
//
724+
// TODO: The container can still be reported as running if the PreExec step
725+
// (e.g., BPF/seccomp filter setup) fails after the caller reported success. We
726+
// should find a way to handle that case as well.
727+
func execMonitor(metrics m.Writer, vmm types.VMM, execArgs types.ExecArgs, execCmd []string) error {
728+
uniklog.Debug("calling vmm execve")
729+
metrics.Capture(m.TS18)
722730
// Perform any monitor-specific pre-exec setup (e.g., seccomp filters for HVT).
723-
err = vmm.PreExec(vmmArgs)
731+
err := vmm.PreExec(execArgs)
724732
if err != nil {
725733
uniklog.WithError(err).Error("failed to perform pre-exec setup")
726734
return err
727735
}
728736

729737
// Execute the VMM using the command we built earlier.
730738
uniklog.WithField("command", execCmd).Debug("Ready to execve VMM")
731-
return syscall.Exec(vmm.Path(), execCmd, vmmArgs.Environment) //nolint: gosec
739+
return syscall.Exec(vmm.Path(), execCmd, execArgs.Environment) //nolint: gosec
732740
}
733741

734742
func setupUser(user specs.User) error {

0 commit comments

Comments
 (0)