Skip to content

Commit

Permalink
Expose function internal and external invocation urls (#2163)
Browse files Browse the repository at this point in the history
  • Loading branch information
liranbg committed Apr 25, 2021
1 parent 90834a4 commit 709f998
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 83 deletions.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -22,7 +22,7 @@ require (
github.com/go-chi/chi v4.0.2+incompatible
github.com/go-chi/cors v1.0.0
github.com/gobuffalo/flect v0.2.2
github.com/google/go-cmp v0.5.4
github.com/google/go-cmp v0.5.5
github.com/hashicorp/go-uuid v1.0.1
github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40
github.com/icza/dyno v0.0.0-20180601094105-0c96289f9585
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -263,8 +263,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
5 changes: 2 additions & 3 deletions pkg/dashboard/test/server_test.go
Expand Up @@ -42,8 +42,8 @@ import (
mockplatform "github.com/nuclio/nuclio/pkg/platform/mock"
"github.com/nuclio/nuclio/pkg/platformconfig"
"github.com/nuclio/nuclio/pkg/restful"
"github.com/nuclio/nuclio/test/compare"

"github.com/google/go-cmp/cmp"
"github.com/nuclio/logger"
"github.com/nuclio/nuclio-sdk-go"
"github.com/nuclio/zap"
Expand Down Expand Up @@ -154,8 +154,7 @@ func (suite *dashboardTestSuite) sendRequest(method string,

err = json.Unmarshal([]byte(typedEncodedExpectedResponse), &decodedExpectedResponseBody)
suite.Require().NoError(err)

suite.Require().True(compare.NoOrder(decodedExpectedResponseBody, decodedResponseBody))
suite.Require().Empty(cmp.Diff(decodedExpectedResponseBody, decodedResponseBody))

case func(response map[string]interface{}) bool:
suite.Require().True(typedEncodedExpectedResponse(decodedResponseBody))
Expand Down
3 changes: 3 additions & 0 deletions pkg/dockerclient/dockerclient.go
Expand Up @@ -88,4 +88,7 @@ type Client interface {

// GetVersion returns docker client and engine versions
GetVersion(quiet bool) (string, error)

// GetContainerIPAddresses return list of container ip addresses
GetContainerIPAddresses(containerID string) ([]string, error)
}
5 changes: 5 additions & 0 deletions pkg/dockerclient/mock.go
Expand Up @@ -151,3 +151,8 @@ func (mdc *MockDockerClient) GetVersion(quiet bool) (string, error) {
args := mdc.Called(quiet)
return args.String(0), args.Error(1)
}

func (mdc *MockDockerClient) GetContainerIPAddresses(containerID string) ([]string, error) {
args := mdc.Called(containerID)
return args.Get(0).([]string), args.Error(1)
}
11 changes: 10 additions & 1 deletion pkg/dockerclient/shell.go
Expand Up @@ -716,7 +716,6 @@ func (c *ShellClient) Save(imageName string, outPath string) error {
func (c *ShellClient) Load(inPath string) error {
c.logger.DebugWith("Docker loading from path", "inPath", inPath)
_, err := c.runCommand(nil, `docker load --input %s`, inPath)

return err
}

Expand All @@ -731,6 +730,16 @@ func (c *ShellClient) GetVersion(quiet bool) (string, error) {
return output.Output, nil
}

func (c *ShellClient) GetContainerIPAddresses(containerID string) ([]string, error) {
c.logger.DebugWith("Getting container IP addresses", "containerID", containerID)
runResults, err := c.runCommand(nil, `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' %s`, containerID)
if err != nil {
return nil, errors.Wrap(err, "Failed to get container ip addresses")
}

return strings.Split(strings.TrimSpace(runResults.Output), "\n"), nil
}

func (c *ShellClient) runCommand(runOptions *cmdrunner.RunOptions, format string, vars ...interface{}) (cmdrunner.RunResult, error) {

// if user
Expand Down
12 changes: 11 additions & 1 deletion pkg/functionconfig/types.go
Expand Up @@ -512,9 +512,19 @@ type Status struct {
State FunctionState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
Logs []map[string]interface{} `json:"logs,omitempty"`
HTTPPort int `json:"httpPort,omitempty"`
ScaleToZero *ScaleToZeroStatus `json:"scaleToZero,omitempty"`
APIGateways []string `json:"apiGateways,omitempty"`
HTTPPort int `json:"httpPort,omitempty"`

// list of internal urls
// e.g.:
// Kubernetes - [ my-namespace.my-function.svc.cluster.local:8080 ]
// Docker - [ function-container-name:8080 ]
InternalInvocationURLs []string `json:"internalInvocationURLs,omitempty"`

// list of external urls, containing ingresses and external-ip:function-port
// e.g.: [ my-function.some-domain.com/pathA, other-ingress.some-domain.co, 1.2.3.4:3000 ]
ExternalInvocationURLs []string `json:"externalInvocationURLs,omitempty"`
}

type ScaleToZeroStatus struct {
Expand Down
27 changes: 0 additions & 27 deletions pkg/nuctl/command/common/helpers.go
Expand Up @@ -2,41 +2,14 @@ package common

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/nuclio/nuclio/pkg/platform"

"github.com/ghodss/yaml"
"github.com/nuclio/errors"
)

func FormatFunctionIngresses(function platform.Function) string {
var formattedIngresses string

ingresses := function.GetIngresses()

for _, ingress := range ingresses {
host := ingress.Host
if host != "" {
host += ":<port>"
}

for _, path := range ingress.Paths {
formattedIngresses += fmt.Sprintf("%s%s, ", host, path)
}
}

// add default ingress
formattedIngresses += fmt.Sprintf("/%s/%s",
function.GetConfig().Meta.Name,
function.GetVersion())

return formattedIngresses
}

func ReadFromInOrStdin(r io.Reader) ([]byte, error) {
switch in := r.(type) {
case *os.File:
Expand Down
7 changes: 5 additions & 2 deletions pkg/nuctl/command/common/renderers.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"strconv"
"strings"
"sync"

"github.com/nuclio/nuclio/pkg/common"
Expand Down Expand Up @@ -49,7 +50,8 @@ func RenderFunctions(logger logger.Logger,
if format == OutputFormatWide {
header = append(header, []string{
"Labels",
"Ingresses",
"Internal Invocation URL",
"External Invocation URLs",
}...)
}

Expand All @@ -73,7 +75,8 @@ func RenderFunctions(logger logger.Logger,
if format == OutputFormatWide {
functionFields = append(functionFields, []string{
common.StringMapToString(function.GetConfig().Meta.Labels),
FormatFunctionIngresses(function),
strings.Join(function.GetStatus().InternalInvocationURLs, ", "),
strings.Join(function.GetStatus().ExternalInvocationURLs, ", "),
}...)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/platform/abstract/platform.go
Expand Up @@ -195,10 +195,10 @@ func (ap *Platform) HandleDeployFunction(existingFunctionConfig *functionconfig.
}

// indicate that we're done
// TODO: print function.Status.Invocation and not just `HTTPPort`
createFunctionOptions.Logger.InfoWith("Function deploy complete",
"functionName", deployResult.UpdatedFunctionConfig.Meta.Name,
"httpPort", deployResult.Port)

return deployResult, nil
}

Expand Down
24 changes: 21 additions & 3 deletions pkg/platform/kube/client/deployer.go
Expand Up @@ -71,14 +71,22 @@ func (d *Deployer) CreateOrUpdateFunction(functionInstance *nuclioio.NuclioFunct
functionInstance = &nuclioio.NuclioFunction{}
functionInstance.Status.State = functionconfig.FunctionStateWaitingForResourceConfiguration
} else {
functionStatus.InternalInvocationURLs = functionInstance.Status.InternalInvocationURLs
functionStatus.ExternalInvocationURLs = functionInstance.Status.ExternalInvocationURLs
functionStatus.HTTPPort = functionInstance.Status.HTTPPort
}

// convert config, status -> function
d.populateFunction(&createFunctionOptions.FunctionConfig, functionStatus, functionInstance, functionExists)
if err := d.populateFunction(&createFunctionOptions.FunctionConfig,
functionStatus,
functionInstance,
functionExists); err != nil {
return nil, errors.Wrap(err, "Failed to populate function")
}

createFunctionOptions.Logger.DebugWith("Populated function with configuration and status",
"function", functionInstance)
"function", functionInstance,
"functionExists", functionExists)

// get clientset
nuclioClientSet, err := d.consumer.getNuclioClientSet(createFunctionOptions.AuthConfig)
Expand Down Expand Up @@ -143,7 +151,7 @@ func (d *Deployer) Deploy(functionInstance *nuclioio.NuclioFunction,
func (d *Deployer) populateFunction(functionConfig *functionconfig.Config,
functionStatus *functionconfig.Status,
functionInstance *nuclioio.NuclioFunction,
functionExisted bool) {
functionExisted bool) error {

functionInstance.Spec = functionConfig.Spec

Expand Down Expand Up @@ -181,6 +189,16 @@ func (d *Deployer) populateFunction(functionConfig *functionconfig.Config,

// update status
functionInstance.Status = *functionStatus

externalIPAddresses, err := d.platform.GetExternalIPAddresses()
if err != nil {
return errors.Wrap(err, "Failed to get external ip address")
}

// -1 because port was not assigned yet, it is just a placeholder
functionInstance.Status.ExternalInvocationURLs = []string{fmt.Sprintf("%s:-1", externalIPAddresses[0])}
return nil

}

func (d *Deployer) getFunctionPodLogsAndEvents(namespace string, name string) (string, string) {
Expand Down
74 changes: 66 additions & 8 deletions pkg/platform/kube/controller/nucliofunction.go
Expand Up @@ -18,19 +18,22 @@ package controller

import (
"context"
"fmt"
"strings"
"time"

"github.com/nuclio/nuclio/pkg/common"
"github.com/nuclio/nuclio/pkg/functionconfig"
"github.com/nuclio/nuclio/pkg/platform/abstract"
nuclioio "github.com/nuclio/nuclio/pkg/platform/kube/apis/nuclio.io/v1beta1"
"github.com/nuclio/nuclio/pkg/platform/kube/client"
"github.com/nuclio/nuclio/pkg/platform/kube/functionres"
"github.com/nuclio/nuclio/pkg/platform/kube/operator"

"github.com/nuclio/errors"
"github.com/nuclio/logger"
"github.com/v3io/scaler-types"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
Expand Down Expand Up @@ -197,17 +200,14 @@ func (fo *functionOperator) CreateOrUpdate(ctx context.Context, object runtime.O
finalState = functionconfig.FunctionStateReady
}

// get function http port
httpPort, err := fo.getFunctionHTTPPort(resources)
if err != nil {
return errors.Wrap(err, "Failed to get function http port")
}

// NOTE: this reconstructs function status and hence omits all other function status fields
// ... such as message and logs.
functionStatus := &functionconfig.Status{
State: finalState,
HTTPPort: httpPort,
State: finalState,
}

if err := fo.populateFunctionInvocationStatus(function, functionStatus, resources); err != nil {
return errors.Wrap(err, "Failed to populate function invocation status")
}

if err := fo.setFunctionScaleToZeroStatus(ctx, functionStatus, scaleEvent); err != nil {
Expand Down Expand Up @@ -312,3 +312,61 @@ func (fo *functionOperator) getFunctionHTTPPort(functionResources functionres.Re
}
return httpPort, nil
}

func (fo *functionOperator) populateFunctionInvocationStatus(function *nuclioio.NuclioFunction,
functionStatus *functionconfig.Status,
functionResources functionres.Resources) error {

// get function http port
httpPort, err := fo.getFunctionHTTPPort(functionResources)
if err != nil {
return errors.Wrap(err, "Failed to get function http port")
}

service, err := functionResources.Service()
if err != nil {
return errors.Wrap(err, "Failed to get function service")
}

ingress, err := functionResources.Ingress()
if err != nil {
return errors.Wrap(err, "Failed to get function ingress")
}

functionStatus.HTTPPort = httpPort

// add internal invocation urls
functionStatus.InternalInvocationURLs = []string{}
if service != nil {
serviceHost, servicePort := client.GetDomainNameInvokeURL(service.GetName(), service.GetNamespace())
functionStatus.InternalInvocationURLs = append(functionStatus.InternalInvocationURLs,
fmt.Sprintf("%s:%d", serviceHost, servicePort))
}

// TODO: move the information on platformConfig and share with controller?
// add external invocation url in form of "external-ip:nodeport"
// first item is being filled by nuclio-dashboard to holds the information regarding the external ip address
if len(function.Status.ExternalInvocationURLs) > 0 && service.Spec.Type == v1.ServiceTypeNodePort {
hostPort := strings.Split(function.Status.ExternalInvocationURLs[0], ":")
functionStatus.ExternalInvocationURLs = []string{fmt.Sprintf("%s:%d", hostPort[0], httpPort)}
} else {
functionStatus.ExternalInvocationURLs = []string{}
}

// add ingresses to external invocation urls
if ingress != nil {
for _, rule := range ingress.Spec.Rules {
host := rule.Host
path := "/"
if rule.HTTP != nil {
if len(rule.HTTP.Paths) > 0 {
path = rule.HTTP.Paths[0].Path
}
}
functionStatus.ExternalInvocationURLs = append(functionStatus.ExternalInvocationURLs,
fmt.Sprintf("%s%s", host, path))
}
}
return nil

}
8 changes: 6 additions & 2 deletions pkg/platform/kube/functionres/lazy.go
Expand Up @@ -159,7 +159,9 @@ func (lc *lazyClient) Get(ctx context.Context, namespace string, name string) (R
}, err
}

func (lc *lazyClient) CreateOrUpdate(ctx context.Context, function *nuclioio.NuclioFunction, imagePullSecrets string) (Resources, error) {
func (lc *lazyClient) CreateOrUpdate(ctx context.Context,
function *nuclioio.NuclioFunction,
imagePullSecrets string) (Resources, error) {
var err error

// get labels from the function and add class labels
Expand Down Expand Up @@ -232,7 +234,9 @@ func (lc *lazyClient) CreateOrUpdate(ctx context.Context, function *nuclioio.Nuc
}
}

lc.logger.DebugWith("Successfully created/updated resources", "functionName", function.Name)
lc.logger.DebugWith("Successfully created/updated resources",
"functionName", function.Name,
"functionNamespace", function.Namespace)
return &resources, nil
}

Expand Down

0 comments on commit 709f998

Please sign in to comment.