Skip to content

Commit 1c8cc7e

Browse files
authored
EIM decomposition - provide scenario specific APIs (#780)
1 parent af32ad6 commit 1c8cc7e

File tree

16 files changed

+905
-52
lines changed

16 files changed

+905
-52
lines changed

apiv2/Makefile

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ COPYRIGHT_BANNER := '1i---\n\# SPDX-FileCopyrightText: (C) 2025 Intel Corporatio
8787
# Security config for Go builds
8888
GOEXTRAFLAGS := $(COMMON_GOEXTRAFLAGS)
8989

90-
generate: buf-gen generate-api ## generate protos, types, server and client
90+
generate: buf-gen generate-api gen-allowed-services ## generate protos, types, server and client
9191

9292
lint: $(OUT_DIR) generate buf-breaking validate-openapi license yamllint go-lint hadolint mdlint ## Run all lint
9393

@@ -234,14 +234,14 @@ buf-update: common-buf-update ## Update buf modules
234234

235235
buf-gen-api: $(VENV_NAME) ## Compile protoc api files into code
236236
set +u; . ./$</bin/activate; set -u ;\
237-
buf --version ;\
238-
HUMAN=true buf generate
237+
buf --version ;\
238+
HUMAN=true buf generate
239239

240240
buf-gen: buf-gen-api oapi-patch oapi-banner ## compile protoc files
241241

242242
buf-lint: ## Lint and format protobuf files
243243
buf --version
244-
# TODO: currently failing buf format and buf lint - need to be fixed
244+
# TODO: currently failing buf format and buf lint - needs to be fixed
245245

246246
buf-breaking: common-buf-breaking
247247

@@ -250,3 +250,6 @@ oasdiff-breaking: # Check for breaking changes in openapi using oasdiff
250250
mkdir -p ${TEMP_BASE_OPENAPI_DIR}
251251
git archive origin/${BASE_BRANCH} ${OPENAPI_PATH} | tar -x -C ${TEMP_BASE_OPENAPI_DIR}
252252
oasdiff breaking --composed "${TEMP_BASE_OPENAPI_DIR}/${OPENAPI_PATH}" "${OPENAPI_PATH}" --fail-on ERR
253+
254+
gen-allowed-services: ## Generate allowed services list for EIM scenarios
255+
go run ./tools/allowedservicesgen

apiv2/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.9.1
1+
2.9.2

apiv2/cmd/api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ func main() {
136136
cfg.Inventory.CertPath,
137137
cfg.Inventory.KeyPath,
138138
cfg.RestServer.Authentication,
139+
cfg.Scenario,
139140
)
140141
}()
141142

apiv2/internal/common/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
WstDefaultMaxConnectionsDescription = "The maximum number of concurrent websocket connections"
3939
EnableAuditing = "enableAuditing"
4040
EnableAuditingDescription = "Flag to enable audit logs for REST API calls."
41+
DefaultScenario = "fulleim"
4142
)
4243

4344
type Traces struct {
@@ -83,6 +84,7 @@ type GlobalConfig struct {
8384
Inventory Southbound
8485
Websocket Websocket
8586
EnableAuditing bool
87+
Scenario string
8688
}
8789

8890
type Websocket struct {
@@ -124,6 +126,7 @@ func DefaultConfig() *GlobalConfig {
124126
EnableAuditing: true,
125127
GRPCAddress: "0.0.0.0:8090",
126128
GRPCEndpoint: "localhost:8090",
129+
Scenario: DefaultScenario,
127130
}
128131
}
129132

@@ -162,6 +165,7 @@ func Config() (*GlobalConfig, error) {
162165
enableAuditing := flag.Bool(EnableAuditing, defaultCfg.EnableAuditing, EnableAuditingDescription)
163166
gRPCEndpoint := flag.String("grpcEndpoint", defaultCfg.GRPCEndpoint, "The endpoint of the gRPC server")
164167
gRPCAddress := flag.String("grpcAddress", defaultCfg.GRPCEndpoint, "The gRPC server address")
168+
scenario := flag.String("scenario", defaultCfg.Scenario, "The deployment scenario name (e.g., 'fulleim', 'vpro')")
165169
flag.Parse()
166170

167171
return &GlobalConfig{
@@ -199,5 +203,6 @@ func Config() (*GlobalConfig, error) {
199203
EnableAuditing: *enableAuditing,
200204
GRPCEndpoint: *gRPCEndpoint,
201205
GRPCAddress: *gRPCAddress,
206+
Scenario: *scenario,
202207
}, nil
203208
}

apiv2/internal/common/utils.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-FileCopyrightText: (C) 2026 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package common
5+
6+
import (
7+
"fmt"
8+
"strings"
9+
)
10+
11+
// BuildAllowedHandlersList builds a map of allowed services based on the scenario.
12+
func BuildAllowedHandlersList(scenarioName string, allowlist map[string][]string,
13+
knownServices map[string]interface{},
14+
) (allowed map[string]struct{}, unknown []string, err error) {
15+
allowedServices, ok := allowlist[scenarioName]
16+
if !ok {
17+
err := fmt.Errorf("unknown scenario %q", scenarioName)
18+
return nil, nil, err
19+
}
20+
21+
allowed = make(map[string]struct{}, len(allowedServices))
22+
unknown = []string{}
23+
for _, serviceName := range allowedServices {
24+
serviceName = strings.TrimSpace(serviceName)
25+
if serviceName == "" {
26+
continue
27+
}
28+
29+
// validate against the list of known services
30+
if _, exists := knownServices[serviceName]; !exists {
31+
unknown = append(unknown, serviceName)
32+
} else {
33+
allowed[serviceName] = struct{}{}
34+
}
35+
}
36+
37+
return allowed, unknown, nil
38+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// SPDX-FileCopyrightText: (C) 2026 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package common_test
5+
6+
import (
7+
"testing"
8+
9+
"github.com/open-edge-platform/infra-core/apiv2/v2/internal/common"
10+
)
11+
12+
func TestBuildAllowedHandlersList(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
scenarioName string
16+
allowlist map[string][]string
17+
knownServices map[string]interface{}
18+
wantLen int
19+
wantErr bool
20+
wantServices []string
21+
}{
22+
{
23+
name: "valid scenario with services",
24+
scenarioName: "test-scenario-full",
25+
allowlist: map[string][]string{
26+
"test-scenario-full": {"HostService", "LocationService", "ProviderService"},
27+
},
28+
knownServices: map[string]interface{}{
29+
"HostService": nil,
30+
"LocationService": nil,
31+
"ProviderService": nil,
32+
},
33+
wantLen: 3,
34+
wantErr: false,
35+
wantServices: []string{"HostService", "LocationService", "ProviderService"},
36+
},
37+
{
38+
name: "unknown scenario",
39+
scenarioName: "test-scenario-unknown",
40+
allowlist: map[string][]string{
41+
"test-scenario-full": {"HostService"},
42+
},
43+
knownServices: map[string]interface{}{
44+
"HostService": nil,
45+
},
46+
wantLen: 0,
47+
wantErr: true,
48+
},
49+
{
50+
name: "scenario with empty service names",
51+
scenarioName: "test-scenario-empty",
52+
allowlist: map[string][]string{
53+
"test-scenario-empty": {"HostService", " ", "", "LocationService"},
54+
},
55+
knownServices: map[string]interface{}{
56+
"HostService": nil,
57+
"LocationService": nil,
58+
},
59+
wantLen: 2,
60+
wantErr: false,
61+
wantServices: []string{"HostService", "LocationService"},
62+
},
63+
{
64+
name: "scenario with no services",
65+
scenarioName: "test-scenario-minimal",
66+
allowlist: map[string][]string{
67+
"test-scenario-minimal": {},
68+
},
69+
knownServices: map[string]interface{}{},
70+
wantLen: 0,
71+
wantErr: false,
72+
},
73+
{
74+
name: "scenario with whitespace in service names",
75+
scenarioName: "test-scenario-whitespace",
76+
allowlist: map[string][]string{
77+
"test-scenario-whitespace": {" HostService ", "LocationService "},
78+
},
79+
knownServices: map[string]interface{}{
80+
"HostService": nil,
81+
"LocationService": nil,
82+
},
83+
wantLen: 2,
84+
wantErr: false,
85+
wantServices: []string{"HostService", "LocationService"},
86+
},
87+
}
88+
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
got, _, err := common.BuildAllowedHandlersList(tt.scenarioName, tt.allowlist, tt.knownServices)
92+
93+
if (err != nil) != tt.wantErr {
94+
t.Errorf("BuildAllowedHandlersList() error = %v, wantErr %v", err, tt.wantErr)
95+
return
96+
}
97+
98+
if err != nil {
99+
return
100+
}
101+
102+
if len(got) != tt.wantLen {
103+
t.Errorf("BuildAllowedHandlersList() got map length %d, want %d", len(got), tt.wantLen)
104+
}
105+
106+
for _, service := range tt.wantServices {
107+
if _, exists := got[service]; !exists {
108+
t.Errorf("BuildAllowedHandlersList() missing service %q in result", service)
109+
}
110+
}
111+
})
112+
}
113+
}
114+
115+
func TestBuildAllowedHandlersList_UnregisteredService(t *testing.T) {
116+
allowlist := map[string][]string{
117+
"test-scenario-unregistered": {"HostService", "UnknownService"},
118+
}
119+
120+
knownServices := map[string]interface{}{
121+
"HostService": nil,
122+
// UnknownService is not in the knownServices map
123+
}
124+
125+
list, _, err := common.BuildAllowedHandlersList("test-scenario-unregistered", allowlist, knownServices)
126+
if err != nil {
127+
t.Errorf("BuildAllowedHandlersList() unexpected error: %v", err)
128+
}
129+
130+
if len(list) != 1 {
131+
t.Errorf("BuildAllowedHandlersList() got map length %d, want 1", len(list))
132+
}
133+
134+
// Only known services should be in the allowed map
135+
if _, exists := list["HostService"]; !exists {
136+
t.Error("BuildAllowedHandlersList() missing HostService in result")
137+
}
138+
if _, exists := list["UnknownService"]; exists {
139+
t.Error("BuildAllowedHandlersList() should not include UnknownService in result")
140+
}
141+
}

apiv2/internal/proxy/server.go

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/open-edge-platform/infra-core/apiv2/v2/internal/common"
1919
restv1 "github.com/open-edge-platform/infra-core/apiv2/v2/internal/pbapi/services/v1"
20+
"github.com/open-edge-platform/infra-core/apiv2/v2/internal/scenario"
2021
api "github.com/open-edge-platform/infra-core/apiv2/v2/pkg/api/v2"
2122
inv_client "github.com/open-edge-platform/infra-core/inventory/v2/pkg/client"
2223
"github.com/open-edge-platform/infra-core/inventory/v2/pkg/logging"
@@ -32,27 +33,26 @@ type serviceClientsSignature func(
3233
endpoint string,
3334
opts []grpc.DialOption) (err error)
3435

35-
// servicesClients defines a list of all gRPC service clients that must be
36-
// registered to serve REST API.
37-
var servicesClients = []serviceClientsSignature{
38-
restv1.RegisterRegionServiceHandlerFromEndpoint,
39-
restv1.RegisterSiteServiceHandlerFromEndpoint,
40-
restv1.RegisterLocationServiceHandlerFromEndpoint,
41-
restv1.RegisterHostServiceHandlerFromEndpoint,
42-
restv1.RegisterOperatingSystemServiceHandlerFromEndpoint,
43-
restv1.RegisterInstanceServiceHandlerFromEndpoint,
44-
restv1.RegisterScheduleServiceHandlerFromEndpoint,
45-
restv1.RegisterWorkloadServiceHandlerFromEndpoint,
46-
restv1.RegisterWorkloadMemberServiceHandlerFromEndpoint,
47-
restv1.RegisterProviderServiceHandlerFromEndpoint,
48-
restv1.RegisterTelemetryLogsGroupServiceHandlerFromEndpoint,
49-
restv1.RegisterTelemetryMetricsGroupServiceHandlerFromEndpoint,
50-
restv1.RegisterTelemetryMetricsProfileServiceHandlerFromEndpoint,
51-
restv1.RegisterTelemetryLogsProfileServiceHandlerFromEndpoint,
52-
restv1.RegisterLocalAccountServiceHandlerFromEndpoint,
53-
restv1.RegisterCustomConfigServiceHandlerFromEndpoint,
54-
restv1.RegisterOSUpdatePolicyHandlerFromEndpoint,
55-
restv1.RegisterOSUpdateRunHandlerFromEndpoint,
36+
// servicesClients maps gRPC service names to their grpc-gateway registration functions.
37+
var servicesClients = map[string]serviceClientsSignature{
38+
"RegionService": restv1.RegisterRegionServiceHandlerFromEndpoint,
39+
"SiteService": restv1.RegisterSiteServiceHandlerFromEndpoint,
40+
"LocationService": restv1.RegisterLocationServiceHandlerFromEndpoint,
41+
"HostService": restv1.RegisterHostServiceHandlerFromEndpoint,
42+
"OperatingSystemService": restv1.RegisterOperatingSystemServiceHandlerFromEndpoint,
43+
"InstanceService": restv1.RegisterInstanceServiceHandlerFromEndpoint,
44+
"ScheduleService": restv1.RegisterScheduleServiceHandlerFromEndpoint,
45+
"WorkloadService": restv1.RegisterWorkloadServiceHandlerFromEndpoint,
46+
"WorkloadMemberService": restv1.RegisterWorkloadMemberServiceHandlerFromEndpoint,
47+
"ProviderService": restv1.RegisterProviderServiceHandlerFromEndpoint,
48+
"TelemetryLogsGroupService": restv1.RegisterTelemetryLogsGroupServiceHandlerFromEndpoint,
49+
"TelemetryMetricsGroupService": restv1.RegisterTelemetryMetricsGroupServiceHandlerFromEndpoint,
50+
"TelemetryMetricsProfileService": restv1.RegisterTelemetryMetricsProfileServiceHandlerFromEndpoint,
51+
"TelemetryLogsProfileService": restv1.RegisterTelemetryLogsProfileServiceHandlerFromEndpoint,
52+
"LocalAccountService": restv1.RegisterLocalAccountServiceHandlerFromEndpoint,
53+
"CustomConfigService": restv1.RegisterCustomConfigServiceHandlerFromEndpoint,
54+
"OSUpdatePolicyService": restv1.RegisterOSUpdatePolicyHandlerFromEndpoint,
55+
"OSUpdateRunService": restv1.RegisterOSUpdateRunHandlerFromEndpoint,
5656
}
5757

5858
const (
@@ -90,18 +90,42 @@ func WrapH(h http.Handler) echo.HandlerFunc {
9090
}
9191

9292
func (m *Manager) setupClients(mux *runtime.ServeMux) error {
93-
for _, serviceClient := range servicesClients {
94-
err := serviceClient(m.ctx, mux, m.cfg.GRPCEndpoint,
93+
scenarioName := m.cfg.Scenario
94+
if scenarioName == "" {
95+
return fmt.Errorf("scenario is not set in config")
96+
}
97+
98+
// build a map of allowed services for quick lookup
99+
allowed, err := BuildAllowedClientList(scenarioName, scenario.Allowlist)
100+
if err != nil {
101+
return err
102+
}
103+
104+
for serviceName, serviceClient := range servicesClients {
105+
if _, isAllowed := allowed[serviceName]; !isAllowed {
106+
zlog.Debug().Str("service", serviceName).Str("scenario", scenarioName).
107+
Msg("skipping service client not allowed for scenario")
108+
continue
109+
}
110+
111+
if err := serviceClient(
112+
m.ctx,
113+
mux,
114+
m.cfg.GRPCEndpoint,
95115
[]grpc.DialOption{
96116
grpc.WithTransportCredentials(insecure.NewCredentials()),
97117
// Use Inventory client max message size, to keep Inventory and API consistent.
98118
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(inv_client.MaxMessageSize)),
99-
})
100-
if err != nil {
101-
zlog.InfraErr(err).Msgf("failed to set service client %v", serviceClient)
119+
},
120+
); err != nil {
121+
zlog.InfraErr(err).Str("service", serviceName).Str("scenario", scenarioName).
122+
Msg("failed to set service client")
102123
return err
103124
}
125+
126+
zlog.Info().Str("service", serviceName).Str("scenario", scenarioName).Msg("registered gRPC client")
104127
}
128+
105129
return nil
106130
}
107131

0 commit comments

Comments
 (0)