@@ -3,15 +3,20 @@ package urunc
33import (
44 "encoding/json"
55 "fmt"
6+ "net"
67 "os"
78 "os/exec"
89 "path/filepath"
910 "regexp"
11+ "runtime"
1012 "strings"
1113 "testing"
1214 "time"
1315
16+ "github.com/asaskevich/govalidator"
1417 common "github.com/nubificus/urunc/tests"
18+ _ "github.com/opencontainers/runc/libcontainer/nsenter"
19+ "github.com/vishvananda/netns"
1520)
1621
1722func TestCrictlHvtRumprunRedis (t * testing.T ) {
@@ -394,6 +399,160 @@ func TestCrictlFCUnikraftNginx(t *testing.T) {
394399 }
395400}
396401
402+ func TestCrictlHTTPStaticNet (t * testing.T ) {
403+ containerImage := "harbor.nbfc.io/nubificus/httpreply-fc:x86_64"
404+ procName := "firecracker"
405+ podConfig := crictlSandboxConfig ("fc-unikraft-knative-sandbox" )
406+
407+ // user-container is used as "io.kubernetes.cri.container-name" annotation by crictl
408+ // in order to trigger the static net mode
409+ containerConfig := crictlContainerConfig ("user-container" , containerImage )
410+
411+ // create config files
412+ cwd , err := os .Getwd ()
413+ if err != nil {
414+ t .Fatal ("Failed to retrieve current directory" )
415+ }
416+ absPodConf := filepath .Join (cwd , "pod.json" )
417+ absContConf := filepath .Join (cwd , "cont.json" )
418+ err = writeToFile (absPodConf , podConfig )
419+ if err != nil {
420+ t .Fatalf ("Failed to write pod config: %v" , err )
421+ }
422+ defer os .Remove (absPodConf )
423+ err = writeToFile (absContConf , containerConfig )
424+ if err != nil {
425+ t .Fatalf ("Failed to write container config: %v" , err )
426+ }
427+ defer os .Remove (absContConf )
428+
429+ // pull image
430+ params := []string {"crictl" , "pull" , containerImage }
431+ cmd := exec .Command (params [0 ], params [1 :]... ) //nolint:gosec
432+ output , err := cmd .CombinedOutput ()
433+ if err != nil {
434+ t .Fatalf ("Failed to pull image: %v\n %s" , err , output )
435+ }
436+
437+ // start unikernel in pod
438+ params = strings .Fields ("crictl run --runtime=urunc cont.json pod.json" )
439+ cmd = exec .Command (params [0 ], params [1 :]... ) //nolint:gosec
440+ output , err = cmd .CombinedOutput ()
441+ if err != nil {
442+ t .Fatalf ("Failed to run unikernel: %v\n %s" , err , output )
443+ }
444+ time .Sleep (2 * time .Second )
445+ proc , err := common .FindProc (procName )
446+ if err != nil {
447+ t .Fatalf ("Failed to find %s process: %v" , procName , err )
448+ }
449+ netNs , err := netns .GetFromPid (int (proc .Pid ))
450+ if err != nil {
451+ t .Fatalf ("Failed to find %s process network namespace: %v" , procName , err )
452+ }
453+ origns , _ := netns .Get ()
454+ defer origns .Close ()
455+ runtime .LockOSThread ()
456+ defer runtime .UnlockOSThread ()
457+ err = netns .Set (netNs )
458+ defer func () {
459+ err := netns .Set (origns )
460+ if err != nil {
461+ t .Fatalf ("Failed to revert to default network nampespace: %v" , err )
462+ }
463+ }()
464+ if err != nil {
465+ t .Fatalf ("Failed to change network namespace: %v" , err )
466+ }
467+ ifaces , err := net .Interfaces ()
468+ if err != nil {
469+ t .Fatalf ("Failed to get all interfaces in current network namespace: %v" , err )
470+ }
471+ var tapUrunc net.Interface
472+ for _ , iface := range ifaces {
473+ if strings .Contains (iface .Name , "urunc" ) {
474+ tapUrunc = iface
475+ break
476+ }
477+ }
478+ if tapUrunc .Name == "" {
479+ var names []string
480+ for _ , iface := range ifaces {
481+ names = append (names , iface .Name )
482+ }
483+ err = fmt .Errorf ("Expected tap0_urunc, got %v" , names )
484+ t .Fatalf ("Failed to find urunc's tap device: %v" , err )
485+ }
486+
487+ addrs , err := tapUrunc .Addrs ()
488+ if err != nil {
489+ t .Fatalf ("Failed to get %s interface's IP addresses: %v" , tapUrunc .Name , err )
490+ }
491+ ipAddr := ""
492+ for _ , addr := range addrs {
493+ tmp := strings .Split (addr .String (), "/" )[0 ]
494+ if govalidator .IsIPv4 (tmp ) {
495+ ipAddr = tmp
496+ break
497+ }
498+ }
499+ if ipAddr == "" {
500+ t .Fatalf ("Failed to get %s interface's IPv4 address" , tapUrunc .Name )
501+ }
502+ parts := strings .Split (ipAddr , "." )
503+ newIP := fmt .Sprintf ("%s.%s.%s.2" , parts [0 ], parts [1 ], parts [2 ])
504+ url := fmt .Sprintf ("http://%s:8080" , newIP )
505+ curlCmd := fmt .Sprintf ("curl %s" , url )
506+ params = strings .Fields (curlCmd )
507+ cmd = exec .Command (params [0 ], params [1 :]... ) //nolint:gosec
508+ output , err = cmd .CombinedOutput ()
509+ if err != nil {
510+ t .Fatalf ("Failed to run curl: %v\n %s" , err , output )
511+ }
512+ if string (output ) == "" {
513+ t .Fatal ("Failed to receive valid response" )
514+ }
515+
516+ // FIXME: Investigate why the GET request using net/http fails, while is successful using curl
517+ //
518+ // client := http.DefaultClient
519+ // client.Timeout = 10 * time.Second
520+ // resp, err := client.Get(url)
521+ // if err != nil {
522+ // t.Logf("Failed to perform GET request to %s: %v", url, err)
523+ // }
524+ // defer resp.Body.Close()
525+ // body, err := io.ReadAll(resp.Body)
526+ // if err != nil {
527+ // t.Logf("Error reading response body: %v", err)
528+ // }
529+ // t.Log(string(body))
530+
531+ // Find pod ID
532+ params = strings .Fields ("crictl pods -q" )
533+ cmd = exec .Command (params [0 ], params [1 :]... ) //nolint:gosec
534+ output , err = cmd .CombinedOutput ()
535+ if err != nil {
536+ t .Fatalf ("Failed to find pod: %v\n %s" , err , output )
537+ }
538+
539+ podID := string (output )
540+ podID = strings .TrimSpace (podID )
541+
542+ // Stop and remove pod
543+ params = strings .Fields ("crictl rmp --force " + podID )
544+ cmd = exec .Command (params [0 ], params [1 :]... ) //nolint:gosec
545+ output , err = cmd .CombinedOutput ()
546+ if err != nil {
547+ t .Fatalf ("Failed to stop and remove pod: %v\n %s" , err , output )
548+ }
549+
550+ proc , _ = common .FindProc (procName )
551+ if proc != nil {
552+ t .Fatalf ("%s process is still alive" , procName )
553+ }
554+ }
555+
397556func crictlSandboxConfig (name string ) string {
398557 return fmt .Sprintf (`{
399558 "metadata": {
0 commit comments