Skip to content

Commit

Permalink
Alpine: adding console node.
Browse files Browse the repository at this point in the history
  • Loading branch information
parth-opensrc authored and parth-opensrc committed Nov 20, 2024
1 parent b25db18 commit 2af8a60
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 4 deletions.
99 changes: 95 additions & 4 deletions topo/node/alpine/alpine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ package alpine
import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"

apb "github.com/openconfig/kne/proto/alpine"
tpb "github.com/openconfig/kne/proto/topo"
Expand All @@ -29,14 +31,21 @@ import (
"k8s.io/utils/pointer"
)

const (
alpineConsoleNodeName = "alpine-console"
)

func New(nodeImpl *node.Impl) (node.Node, error) {
if nodeImpl == nil {
return nil, fmt.Errorf("nodeImpl cannot be nil")
}
if nodeImpl.Proto == nil {
return nil, fmt.Errorf("nodeImpl.Proto cannot be nil")
}
cfg := defaults(nodeImpl.Proto)
cfg, err := defaults(nodeImpl.Proto)
if err != nil {
return nil, fmt.Errorf("fetching alpine default config failed, err: %v", err)
}
nodeImpl.Proto = cfg
n := &Node{
Impl: nodeImpl,
Expand Down Expand Up @@ -77,10 +86,75 @@ func (n *Node) Create(ctx context.Context) error {
return nil
}

// Taken from https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
func outboundIP() (string, error) {
// Dial Google DNS using RFC863 (Discard Protocol)
// NB this doesn't actually do internet, it's just a trick to give us a
// realistic guess of which network interface is the relevant one.
// Get preferred outbound ip of this machine.
conn, err := net.DialTimeout("udp", "8.8.8.8:80", time.Minute)
if err != nil {
return "", fmt.Errorf("failed to dial Google DNS: %w", err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String(), nil
}

// createConsoleCMD uses socat to pipe to the host SSH.
func createConsoleCMD(topo *tpb.Node) ([]string, error) {
ip, err := outboundIP()
if err != nil {
return nil, fmt.Errorf("unable to get local IP: %v", err)
}
cmd := fmt.Sprintf("socat TCP-LISTEN:2222,fork,reuseaddr TCP4:%v:22", ip)
log.Infof("Alpine console: using command ", cmd)
return strings.Split(cmd, " "), nil
}

// createConsolePod creates a SSHable pod for Alpine.
func (n *Node) createConsolePod(ctx context.Context, topo *tpb.Node) error {
log.Infof("Creating Console Pod for Alpine:\n")
pb := n.Proto
containerSpec := corev1.Container{
Name: "console-container",
Image: pb.Config.Image,
Command: pb.Config.Command,
ImagePullPolicy: "IfNotPresent",
SecurityContext: &corev1.SecurityContext{
Privileged: pointer.Bool(true),
},
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: topo.Name,
Labels: map[string]string{
"app": topo.Name,
"topo": n.Namespace,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{containerSpec},
TerminationGracePeriodSeconds: pointer.Int64(0),
NodeSelector: map[string]string{},
HostNetwork: true,
},
}
sPod, err := n.KubeClient.CoreV1().Pods(n.Namespace).Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
return err
}
log.Infof("Alpine console: Pod created:\n%+v\n", sPod)
return nil
}

// CreatePod creates a Pod for the Node based on the underlying proto.
func (n *Node) CreatePod(ctx context.Context) error {
pb := n.Proto
log.Infof("Creating Pod:\n %+v", pb)
if pb.Name == alpineConsoleNodeName {
return n.createConsolePod(ctx, pb)
}

initContainerImage := pb.Config.InitImage
if initContainerImage == "" {
Expand Down Expand Up @@ -248,10 +322,28 @@ func (n *Node) CreatePod(ctx context.Context) error {
return nil
}

func defaults(pb *tpb.Node) *tpb.Node {
func consoleDefaults(pb *tpb.Node) (*tpb.Node, error) {
if len(pb.GetConfig().Command) == 0 {
cmd, err := createConsoleCMD(pb)
if err != nil {
return pb, fmt.Errorf("createConsoleCMD failed, err: %v", err)
}
pb.Config.Command = cmd
}
if pb.Config.GetImage() == "" {
// use a light weight image with socat.
pb.Config.Image = "alpine/socat"
}
return pb, nil
}

func defaults(pb *tpb.Node) (*tpb.Node, error) {
if pb.Config == nil {
pb.Config = &tpb.Config{}
}
if pb.GetName() == alpineConsoleNodeName {
return consoleDefaults(pb)
}
if len(pb.GetConfig().GetCommand()) == 0 {
pb.Config.Command = []string{"go", "run", "main.go"}
}
Expand All @@ -270,8 +362,7 @@ func defaults(pb *tpb.Node) *tpb.Node {
}
}
// TODO: Add appropriate default constraints for the Alpine KNE node

return pb
return pb, nil
}

func init() {
Expand Down
39 changes: 39 additions & 0 deletions topo/node/alpine/alpine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ func TestNew(t *testing.T) {
},
},
},
}, {
desc: "with console",
nImpl: &node.Impl{
Proto: &tpb.Node{
Name: "alpine-console",
Config: &tpb.Config{
Command: []string{"ls"},
},
},
},
want: &tpb.Node{
Name: "alpine-console",
Config: &tpb.Config{
Image: "alpine/socat",
Command: []string{"ls"},
},
},
}}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
Expand Down Expand Up @@ -288,6 +305,28 @@ func TestCreatePod(t *testing.T) {
Privileged: pointer.Bool(true),
},
},
}, {
desc: "get alpine console containers",
nImpl: &node.Impl{
Proto: &tpb.Node{
Name: "alpine-console",
Vendor: tpb.Vendor_ALPINE,
Config: &tpb.Config{
Image: "alpine/socat",
Command: []string{"alpineCommand"},
},
},
},
wantAlpineCtr: corev1.Container{
Name: "console-container",
Image: "alpine/socat",
Command: []string{"alpineCommand"},
Resources: corev1.ResourceRequirements{},
ImagePullPolicy: "IfNotPresent",
SecurityContext: &corev1.SecurityContext{
Privileged: pointer.Bool(true),
},
},
}}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
Expand Down

0 comments on commit 2af8a60

Please sign in to comment.