Skip to content

Commit

Permalink
feat: BFF is able to run (mocked) without Kubernetes (#190)
Browse files Browse the repository at this point in the history
In this PR:
- Kubernetes client mock
- Update on BFF readme

Signed-off-by: Eder Ignatowicz <[email protected]>
  • Loading branch information
ederign authored Jul 18, 2024
1 parent a5042d6 commit 1d37fa5
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 28 deletions.
6 changes: 4 additions & 2 deletions clients/ui/bff/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
CONTAINER_TOOL ?= docker
IMG ?= model-registry-bff:latest
PORT ?= 4000
MOCK_K8S_CLIENT ?= false

.PHONY: all
all: build
Expand Down Expand Up @@ -30,8 +32,8 @@ build: fmt vet test

.PHONY: run
run: fmt vet
PORT=4000 go run ./cmd/main.go
go run ./cmd/main.go --port=$(PORT) --mock-k8s-client=$(MOCK_K8S_CLIENT)

.PHONY: docker-build
docker-build:
$(CONTAINER_TOOL) build -t ${IMG} .
$(CONTAINER_TOOL) build -t ${IMG} .
51 changes: 47 additions & 4 deletions clients/ui/bff/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,54 @@
# Kubeflow Model Registry UI BFF
The Kubeflow Model Registry UI BFF is the _backend for frontend_ (BFF) used by the Kubeflow Model Registry UI.

# Building and Deploying
TBD
## Pre-requisites:

### Dependencies
- Go >= 1.22.2

### Running model registry & ml-metadata
To be operational, our BFF needs the Model Registry & Ml-metadata backend running.

> **NOTE:** Docker compose must be installed in your environment.
There are two `docker-compose` files located at the [root](https://github.com/kubeflow/model-registry) of Model Registry repository that make the startup of both model registry and ml-metadata easier by simply running:

```shell
docker compose -f docker-compose[-local].yaml up
```

The main difference between the two docker compose files is that `-local` one builds the model registry from source, the other one, instead, download the `latest` pushed [quay.io](https://quay.io/repository/opendatahub/model-registry?tab=tags) image.

When shutting down the docker compose, you might want to clean-up the SQLite db file generated by ML Metadata, for example `./test/config/ml-metadata/metadata.sqlite.db`

# Development
TBD

Run the following command to build the BFF:
```shell
make build
```
After building it, you can run our app with:
```shell
make run
```
If you want to use a different port or mock kubernetes client, useful for front-end development, you can run:
```shell
make run PORT=8000 MOCK_K8S_CLIENT=true
```

# Building and Deploying

Run the following command to build the BFF:
```shell
make build
```
The BFF binary will be inside `bin` directory

You can also build BFF docker image with:
```shell
make docker-build
```


## Getting started

Expand Down Expand Up @@ -48,4 +91,4 @@ curl -i -X POST "http://localhost:4000/api/v1/model-registry/model-registry/regi
"owner": "eder",
"state": "LIVE"
}'
```
```
11 changes: 10 additions & 1 deletion clients/ui/bff/api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/kubeflow/model-registry/ui/bff/config"
"github.com/kubeflow/model-registry/ui/bff/data"
"github.com/kubeflow/model-registry/ui/bff/integrations"
"github.com/kubeflow/model-registry/ui/bff/internals/mocks"
"log/slog"
"net/http"

Expand All @@ -28,7 +29,15 @@ type App struct {
}

func NewApp(cfg config.EnvConfig, logger *slog.Logger) (*App, error) {
k8sClient, err := integrations.NewKubernetesClient(logger)
var k8sClient integrations.KubernetesClientInterface
var err error
if cfg.MockK8Client {
//mock all k8s calls
k8sClient, err = mocks.NewKubernetesClient(logger)
} else {
k8sClient, err = integrations.NewKubernetesClient(logger)
}

if err != nil {
return nil, fmt.Errorf("failed to create Kubernetes client: %w", err)
}
Expand Down
9 changes: 4 additions & 5 deletions clients/ui/bff/api/model_registry_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import (
)

func TestModelRegistryHandler(t *testing.T) {
mockK8sClient := new(mocks.KubernetesClientMock)
mockK8sClient.On("GetServiceNames").Return(mockK8sClient.MockServiceNames(), nil)
mockK8sClient, _ := mocks.NewKubernetesClient(nil)

testApp := App{
kubernetesClient: mockK8sClient,
Expand Down Expand Up @@ -46,12 +45,12 @@ func TestModelRegistryHandler(t *testing.T) {

var expected = Envelope{
"model_registry": []data.ModelRegistryModel{
{Name: mockK8sClient.MockServiceNames()[0]},
{Name: mockK8sClient.MockServiceNames()[1]},
{Name: "model-registry"},
{Name: "model-registry-dora"},
{Name: "model-registry-bella"},
},
}

assert.Equal(t, expected, modelRegistryRes)

mockK8sClient.AssertExpectations(t)
}
2 changes: 2 additions & 0 deletions clients/ui/bff/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
func main() {
var cfg config.EnvConfig
flag.IntVar(&cfg.Port, "port", getEnvAsInt("PORT", 4000), "API server port")
flag.BoolVar(&cfg.MockK8Client, "mock-k8s-client", false, "Use mock Kubernetes client")
flag.Parse()

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

Expand Down
3 changes: 2 additions & 1 deletion clients/ui/bff/config/environment.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package config

type EnvConfig struct {
Port int
Port int
MockK8Client bool
}
10 changes: 4 additions & 6 deletions clients/ui/bff/data/model_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import (
)

func TestFetchAllModelRegistry(t *testing.T) {
mockK8sClient := new(mocks.KubernetesClientMock)

mockK8sClient.On("GetServiceNames").Return(mockK8sClient.MockServiceNames(), nil)
mockK8sClient, _ := mocks.NewKubernetesClient(nil)

model := ModelRegistryModel{}

Expand All @@ -18,10 +16,10 @@ func TestFetchAllModelRegistry(t *testing.T) {
assert.NoError(t, err)

expectedRegistries := []ModelRegistryModel{
{Name: mockK8sClient.MockServiceNames()[0]},
{Name: mockK8sClient.MockServiceNames()[1]},
{Name: "model-registry"},
{Name: "model-registry-dora"},
{Name: "model-registry-bella"},
}
assert.Equal(t, expectedRegistries, registries)

mockK8sClient.AssertExpectations(t)
}
22 changes: 13 additions & 9 deletions clients/ui/bff/internals/mocks/k8s_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ package mocks
import (
k8s "github.com/kubeflow/model-registry/ui/bff/integrations"
"github.com/stretchr/testify/mock"
"log/slog"
)

type KubernetesClientMock struct {
mock.Mock
}

func NewKubernetesClient(logger *slog.Logger) (k8s.KubernetesClientInterface, error) {
return &KubernetesClientMock{}, nil
}

func (m *KubernetesClientMock) GetServiceNames() ([]string, error) {
args := m.Called()
return args.Get(0).([]string), args.Error(1)
return []string{"model-registry", "model-registry-dora", "model-registry-bella"}, nil
}

func (m *KubernetesClientMock) BearerToken() (string, error) {
args := m.Called()
return args.String(0), args.Error(1)
return "FAKE BEARER TOKEN", nil
}

func (m *KubernetesClientMock) GetServiceDetailsByName(serviceName string) (k8s.ServiceDetails, error) {
args := m.Called(serviceName)
return args.Get(0).(k8s.ServiceDetails), args.Error(1)
}
func (m *KubernetesClientMock) MockServiceNames() []string {
return []string{"model-registry-dora", "model-registry-bella"}
//expected forward to docker compose -f docker-compose.yaml up
return k8s.ServiceDetails{
Name: serviceName,
ClusterIP: "127.0.0.1",
HTTPPort: 8080,
}, nil
}

0 comments on commit 1d37fa5

Please sign in to comment.