Skip to content

Commit 6199211

Browse files
authored
Add fuzzing test to app-orch-tenant controller (#29)
1 parent ee81499 commit 6199211

File tree

10 files changed

+367
-25
lines changed

10 files changed

+367
-25
lines changed

.github/workflows/go-fuzz.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
# SPDX-FileCopyrightText: (C) 2025 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
name: Go Fuzzing Tests
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
run_tenant_controller:
11+
description: 'Run Tenant Controller fuzzing tests'
12+
required: false
13+
type: boolean
14+
default: true
15+
fuzz_seconds_tenant_controller:
16+
description: 'Duration per test case in secs. Total duration is secs x # of test cases'
17+
required: false
18+
type: number
19+
default: 60
20+
# Scheduled workflows will only run on the default branch. Input values from workflow_dispatch will be null when schedule event is triggered
21+
schedule:
22+
- cron: "0 0 * * 6" # every week, at 00:00 on Saturday
23+
24+
permissions:
25+
contents: read
26+
27+
jobs:
28+
go-fuzz-tenant-controller:
29+
if: ${{ inputs.run_tenant_controller || github.event_name == 'schedule' }}
30+
name: Tenant Controller Go Fuzzing Tests
31+
uses: open-edge-platform/orch-ci/.github/workflows/apporch-go-fuzz.yml@4c94cdd01e58beab5f822f1eeb0439a523018a55 # v0.1.5
32+
with:
33+
# Declare 4800 secs duration since schedule event will not pick up input values from workflow_dispatch
34+
fuzz_seconds: ${{ fromJSON(inputs.fuzz_seconds_tenant_controller || 4800) }}
35+
test_data_dir: ./internal/nexus/testdata/fuzz

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ DOCKER_REGISTRY ?= registry-rs.edgeorchestration.intel.com
2424
PUBLISH_SUB_PROJ ?= app
2525
PUBLISH_CHART_PREFIX ?= charts
2626

27+
FUZZ_SECONDS ?= 60
28+
2729
DOCKER_TAG := $(PUBLISH_REGISTRY)/$(PUBLISH_REPOSITORY)/$(PUBLISH_SUB_PROJ)/$(PUBLISH_NAME):$(VERSION)
2830
DOCKER_BUILD_COMMAND := docker buildx build
2931

@@ -104,6 +106,18 @@ go-test: ## Runs test stage
104106
$(GOCMD) test -race -gcflags=-l `go list $(PKG)/pkg/... | grep -v "/mocks" | grep -v "/test/"`
105107
@echo "---END MAKEFILE TEST---"
106108

109+
FUZZ_FUNCS ?= FuzzCreateProject FuzzDeleteProject
110+
FUZZ_FUNC_PATH := ./internal/nexus
111+
112+
.PHONY: go-fuzz
113+
go-fuzz: ## GO fuzz tests
114+
for func in $(FUZZ_FUNCS); do \
115+
$(GOCMD) test $(FUZZ_FUNC_PATH) -fuzz $$func -fuzztime=${FUZZ_SECONDS}s -v; \
116+
done
117+
118+
go-format: ## Help: Formats go source files
119+
@go fmt $(shell sh -c "go list ./...")
120+
107121
.PHONY: go-cover-dependency
108122
go-cover-dependency: ## installs the gocover tool
109123
go tool cover -V || go install golang.org/x/tools/cmd/cover@${GOLANG_COVER_VERSION}

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.2
1+
0.3.3

deploy/charts/app-orch-tenant-controller/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
apiVersion: v2
66
description: Tenant Controller
77
name: app-orch-tenant-controller
8-
version: 0.3.2
8+
version: 0.3.3
99
annotations:
1010
revision: ""
1111
created: ""
12-
appVersion: "0.3.2"
12+
appVersion: "0.3.3"

internal/manager/manager.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
nexushook "github.com/open-edge-platform/app-orch-tenant-controller/internal/nexus"
1111
"github.com/open-edge-platform/app-orch-tenant-controller/internal/plugins"
1212
"github.com/open-edge-platform/orch-library/go/dazl"
13-
nexus "github.com/open-edge-platform/orch-utils/tenancy-datamodel/build/nexus-client"
1413
"google.golang.org/grpc"
1514
"google.golang.org/grpc/health"
1615
"google.golang.org/grpc/health/grpc_health_v1"
@@ -162,7 +161,7 @@ func (m *Manager) handleProjectEvent(event plugins.Event) error {
162161
return err
163162
}
164163

165-
func (m *Manager) CreateProject(organizationName string, projectName string, projectUUID string, project *nexus.RuntimeprojectRuntimeProject) {
164+
func (m *Manager) CreateProject(organizationName string, projectName string, projectUUID string, project nexushook.NexusProjectInterface) {
166165
log.Debugf("Creating project with organizationName=%s; projectName=%s; projectUUID=%s", organizationName, projectName, projectUUID)
167166
e := plugins.Event{
168167
EventType: "create",
@@ -174,7 +173,7 @@ func (m *Manager) CreateProject(organizationName string, projectName string, pro
174173
m.eventChan <- e
175174
}
176175

177-
func (m *Manager) DeleteProject(organizationName string, projectName string, projectUUID string, project *nexus.RuntimeprojectRuntimeProject) {
176+
func (m *Manager) DeleteProject(organizationName string, projectName string, projectUUID string, project nexushook.NexusProjectInterface) {
178177
log.Debugf("Deleting project with organizationName=%s; projectName=%s; projectUUID=%s", organizationName, projectName, projectUUID)
179178
e := plugins.Event{
180179
EventType: "delete",

internal/nexus/mock-nexus_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// SPDX-FileCopyrightText: (C) 2024 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nexus
5+
6+
import (
7+
"context"
8+
projectActiveWatcherv1 "github.com/open-edge-platform/orch-utils/tenancy-datamodel/build/apis/projectactivewatcher.edge-orchestrator.intel.com/v1"
9+
nexus "github.com/open-edge-platform/orch-utils/tenancy-datamodel/build/nexus-client"
10+
)
11+
12+
// This module contains mocks for the Nexus client. It maintains an in-memory list of watchers.
13+
14+
type MockNexusOrganization struct {
15+
}
16+
17+
func (o *MockNexusOrganization) DisplayName() string {
18+
return "MockNexusOrganization"
19+
}
20+
21+
type MockNexusFolder struct {
22+
parent *MockNexusOrganization
23+
}
24+
25+
func (f *MockNexusFolder) GetParent(ctx context.Context) (NexusOrganizationInterface, error) {
26+
_ = ctx
27+
return f.parent, nil
28+
}
29+
30+
type MockNexusProject struct {
31+
isDeleted bool
32+
displayName string
33+
uid string
34+
parent *MockNexusFolder
35+
activeWatchers map[string]*nexus.ProjectactivewatcherProjectActiveWatcher
36+
}
37+
38+
func (p *MockNexusProject) GetActiveWatchers(ctx context.Context, name string) (*nexus.ProjectactivewatcherProjectActiveWatcher, error) {
39+
_ = ctx
40+
return p.activeWatchers[name], nil
41+
}
42+
43+
func (p *MockNexusProject) AddActiveWatchers(ctx context.Context, watcher *projectActiveWatcherv1.ProjectActiveWatcher) (*nexus.ProjectactivewatcherProjectActiveWatcher, error) {
44+
_ = ctx
45+
p.activeWatchers[watcher.Name] = &nexus.ProjectactivewatcherProjectActiveWatcher{ProjectActiveWatcher: watcher}
46+
47+
return p.activeWatchers[watcher.Name], nil
48+
}
49+
50+
func (p *MockNexusProject) DeleteActiveWatchers(ctx context.Context, name string) error {
51+
_ = ctx
52+
_ = name
53+
return nil
54+
}
55+
56+
func (p *MockNexusProject) GetParent(ctx context.Context) (NexusFolderInterface, error) {
57+
_ = ctx
58+
return p.parent, nil
59+
}
60+
61+
func (p *MockNexusProject) DisplayName() string {
62+
return p.displayName
63+
}
64+
65+
func (p *MockNexusProject) GetUID() string {
66+
return p.uid
67+
}
68+
69+
func (p *MockNexusProject) IsDeleted() bool {
70+
return p.isDeleted
71+
}
72+
73+
func NewMockNexusProject(name string, uid string) *MockNexusProject {
74+
return &MockNexusProject{
75+
isDeleted: false,
76+
displayName: name,
77+
uid: uid,
78+
parent: &MockNexusFolder{},
79+
activeWatchers: make(map[string]*nexus.ProjectactivewatcherProjectActiveWatcher),
80+
}
81+
}

internal/nexus/nexus-abstract.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-FileCopyrightText: (C) 2024 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nexus
5+
6+
import (
7+
"context"
8+
projectActiveWatcherv1 "github.com/open-edge-platform/orch-utils/tenancy-datamodel/build/apis/projectactivewatcher.edge-orchestrator.intel.com/v1"
9+
nexus "github.com/open-edge-platform/orch-utils/tenancy-datamodel/build/nexus-client"
10+
)
11+
12+
// This module creates an inferface and abstraction layer for the Nexus API that allows it to easily be mocked.
13+
14+
type NexusOrganizationInterface interface { // nolint:revive
15+
DisplayName() string
16+
}
17+
18+
type NexusFolderInterface interface { // nolint:revive
19+
GetParent(ctx context.Context) (NexusOrganizationInterface, error)
20+
}
21+
22+
type NexusProjectInterface interface { // nolint:revive
23+
GetActiveWatchers(ctx context.Context, name string) (*nexus.ProjectactivewatcherProjectActiveWatcher, error)
24+
AddActiveWatchers(ctx context.Context, watcher *projectActiveWatcherv1.ProjectActiveWatcher) (*nexus.ProjectactivewatcherProjectActiveWatcher, error)
25+
DeleteActiveWatchers(ctx context.Context, name string) error
26+
GetParent(ctx context.Context) (NexusFolderInterface, error)
27+
DisplayName() string
28+
GetUID() string
29+
IsDeleted() bool
30+
}
31+
32+
// NexusFolder is a wrapper around the Nexus RuntimefolderRuntimeFolder type
33+
34+
type NexusFolder nexus.RuntimefolderRuntimeFolder // nolint:revive
35+
36+
func (f *NexusFolder) GetParent(ctx context.Context) (NexusOrganizationInterface, error) {
37+
return (*nexus.RuntimefolderRuntimeFolder)(f).GetParent(ctx)
38+
}
39+
40+
// NexusProject is a wrapper around the Nexus RuntimeprojectRuntimeProject type
41+
42+
type NexusProject nexus.RuntimeprojectRuntimeProject // nolint:revive
43+
44+
func (p *NexusProject) GetActiveWatchers(ctx context.Context, name string) (*nexus.ProjectactivewatcherProjectActiveWatcher, error) {
45+
return (*nexus.RuntimeprojectRuntimeProject)(p).GetActiveWatchers(ctx, name)
46+
}
47+
48+
func (p *NexusProject) AddActiveWatchers(ctx context.Context, watcher *projectActiveWatcherv1.ProjectActiveWatcher) (*nexus.ProjectactivewatcherProjectActiveWatcher, error) {
49+
return (*nexus.RuntimeprojectRuntimeProject)(p).AddActiveWatchers(ctx, watcher)
50+
}
51+
52+
func (p *NexusProject) DeleteActiveWatchers(ctx context.Context, name string) error {
53+
return (*nexus.RuntimeprojectRuntimeProject)(p).DeleteActiveWatchers(ctx, name)
54+
}
55+
56+
func (p *NexusProject) GetParent(ctx context.Context) (NexusFolderInterface, error) {
57+
folder, err := (*nexus.RuntimeprojectRuntimeProject)(p).GetParent(ctx)
58+
return (*NexusFolder)(folder), err
59+
}
60+
61+
func (p *NexusProject) DisplayName() string {
62+
return (*nexus.RuntimeprojectRuntimeProject)(p).DisplayName()
63+
}
64+
65+
func (p *NexusProject) GetUID() string {
66+
return string((*nexus.RuntimeprojectRuntimeProject)(p).UID)
67+
}
68+
69+
func (p *NexusProject) IsDeleted() bool {
70+
return p.Spec.Deleted
71+
}

internal/nexus/nexus-hook.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const (
2222
)
2323

2424
type ProjectManager interface {
25-
CreateProject(orgName string, projectName string, projectUUID string, project *nexus.RuntimeprojectRuntimeProject)
26-
DeleteProject(orgName string, projectName string, projectUUID string, project *nexus.RuntimeprojectRuntimeProject)
25+
CreateProject(orgName string, projectName string, projectUUID string, project NexusProjectInterface)
26+
DeleteProject(orgName string, projectName string, projectUUID string, project NexusProjectInterface)
2727
}
2828

2929
type Hook struct {
@@ -65,12 +65,12 @@ func (h *Hook) Subscribe() error {
6565
// API to subscribe and register a callback function that is invoked when a Project is added in the datamodel.
6666
// Register*Callback() has the effect of subscription and also invoking a callback to the application code
6767
// when there are datamodel changes to the objects of interest.
68-
if _, err := h.nexusClient.TenancyMultiTenancy().Runtime().Orgs("*").Folders("*").Projects("*").RegisterAddCallback(h.projectCreated); err != nil {
68+
if _, err := h.nexusClient.TenancyMultiTenancy().Runtime().Orgs("*").Folders("*").Projects("*").RegisterAddCallback(h.projectCreatedCallback); err != nil {
6969
log.Errorf("Unable to register project creation callback: %+v", err)
7070
return err
7171
}
7272

73-
if _, err := h.nexusClient.TenancyMultiTenancy().Runtime().Orgs("*").Folders("*").Projects("*").RegisterUpdateCallback(h.projectUpdated); err != nil {
73+
if _, err := h.nexusClient.TenancyMultiTenancy().Runtime().Orgs("*").Folders("*").Projects("*").RegisterUpdateCallback(h.projectUpdatedCallback); err != nil {
7474
log.Errorf("Unable to register project deletion callback: %+v", err)
7575
return err
7676
}
@@ -119,7 +119,7 @@ func (h *Hook) setProjWatcherStatus(watcherObj *nexus.ProjectactivewatcherProjec
119119
return nil
120120
}
121121

122-
func (h *Hook) SetWatcherStatusIdle(proj *nexus.RuntimeprojectRuntimeProject) error {
122+
func (h *Hook) SetWatcherStatusIdle(proj NexusProjectInterface) error {
123123
watcherObj, err := proj.GetActiveWatchers(context.Background(), appName)
124124
if err == nil && watcherObj != nil {
125125
// If watcher exists and is IDLE, simply return.
@@ -139,7 +139,7 @@ func (h *Hook) SetWatcherStatusIdle(proj *nexus.RuntimeprojectRuntimeProject) er
139139
return err
140140
}
141141

142-
func (h *Hook) SetWatcherStatusError(proj *nexus.RuntimeprojectRuntimeProject, message string) error {
142+
func (h *Hook) SetWatcherStatusError(proj NexusProjectInterface, message string) error {
143143
watcherObj, err := proj.GetActiveWatchers(context.Background(), appName)
144144
if err == nil && watcherObj != nil {
145145
setStatusErr := h.setProjWatcherStatus(watcherObj, projectActiveWatcherv1.StatusIndicationError, message)
@@ -152,7 +152,7 @@ func (h *Hook) SetWatcherStatusError(proj *nexus.RuntimeprojectRuntimeProject, m
152152
return err
153153
}
154154

155-
func (h *Hook) SetWatcherStatusInProgress(proj *nexus.RuntimeprojectRuntimeProject, message string) error {
155+
func (h *Hook) SetWatcherStatusInProgress(proj NexusProjectInterface, message string) error {
156156
watcherObj, err := proj.GetActiveWatchers(context.Background(), appName)
157157
log.Infof("Setting watcher status to InProgress for project %s to %s", proj.DisplayName(), message)
158158
if err == nil && watcherObj != nil {
@@ -166,7 +166,7 @@ func (h *Hook) SetWatcherStatusInProgress(proj *nexus.RuntimeprojectRuntimeProje
166166
return err
167167
}
168168

169-
func (h *Hook) StopWatchingProject(project *nexus.RuntimeprojectRuntimeProject) {
169+
func (h *Hook) StopWatchingProject(project NexusProjectInterface) {
170170
ctx, cancel := context.WithTimeout(context.Background(), nexusTimeout)
171171
defer cancel()
172172

@@ -183,18 +183,23 @@ func (h *Hook) StopWatchingProject(project *nexus.RuntimeprojectRuntimeProject)
183183
log.Infof("Active watcher %s deleted for project %s", appName, project.DisplayName())
184184
}
185185

186-
func (h *Hook) deleteProject(project *nexus.RuntimeprojectRuntimeProject) {
186+
func (h *Hook) deleteProject(project NexusProjectInterface) {
187187
log.Infof("Project: %+v marked for deletion", project.DisplayName())
188188

189189
organizationName := h.getOrganizationName(project)
190-
h.dispatcher.DeleteProject(organizationName, project.DisplayName(), string(project.UID), project)
190+
h.dispatcher.DeleteProject(organizationName, project.DisplayName(), project.GetUID(), project)
191+
}
192+
193+
func (h *Hook) projectCreatedCallback(nexusProject *nexus.RuntimeprojectRuntimeProject) {
194+
project := (*NexusProject)(nexusProject)
195+
h.projectCreated(project)
191196
}
192197

193198
// Callback function to be invoked when Project is added.
194-
func (h *Hook) projectCreated(project *nexus.RuntimeprojectRuntimeProject) {
195-
log.Infof("Runtime Project: %+v created", *project)
199+
func (h *Hook) projectCreated(project NexusProjectInterface) {
200+
log.Infof("Runtime Project: %+v created", project.DisplayName())
196201

197-
if project.Spec.Deleted {
202+
if project.IsDeleted() {
198203
log.Info("Created event for deleted project, dispatching delete event")
199204
h.deleteProject(project)
200205
return
@@ -228,12 +233,12 @@ func (h *Hook) projectCreated(project *nexus.RuntimeprojectRuntimeProject) {
228233

229234
// handle the creation of the project
230235
organizationName := h.getOrganizationName(project)
231-
h.dispatcher.CreateProject(organizationName, project.DisplayName(), string(project.UID), project)
236+
h.dispatcher.CreateProject(organizationName, project.DisplayName(), project.GetUID(), project)
232237

233238
log.Infof("Active watcher %s created for Project %s", watcherObj.DisplayName(), project.DisplayName())
234239
}
235240

236-
func (h *Hook) getOrganizationName(project *nexus.RuntimeprojectRuntimeProject) string {
241+
func (h *Hook) getOrganizationName(project NexusProjectInterface) string {
237242
ctx, cancel := context.WithTimeout(context.Background(), nexusTimeout)
238243
defer cancel()
239244

@@ -253,8 +258,13 @@ func (h *Hook) getOrganizationName(project *nexus.RuntimeprojectRuntimeProject)
253258
}
254259

255260
// Callback function to be invoked when Project is deleted.
256-
func (h *Hook) projectUpdated(_, project *nexus.RuntimeprojectRuntimeProject) {
257-
if project.Spec.Deleted {
261+
func (h *Hook) projectUpdatedCallback(_, nexusProject *nexus.RuntimeprojectRuntimeProject) {
262+
project := (*NexusProject)(nexusProject)
263+
h.projectUpdated(project)
264+
}
265+
266+
func (h *Hook) projectUpdated(project NexusProjectInterface) {
267+
if project.IsDeleted() {
258268
h.deleteProject(project)
259269
}
260270
}

0 commit comments

Comments
 (0)