diff --git a/design-proposals/eim-nbapi-cli-decomposition.md b/design-proposals/eim-nbapi-cli-decomposition.md new file mode 100644 index 000000000..e2969014a --- /dev/null +++ b/design-proposals/eim-nbapi-cli-decomposition.md @@ -0,0 +1,182 @@ +# Design Proposal: Exposing only the required North Bound APIs and CLI commands for the workflow as part of EIM decomposition + +Author(s) EIM-Core team + +Last updated: 7/11/25 + +## Abstract + +In context of EIM decomposition the North Bound API service should be treated as an independent interchangeable module. +The [EIM proposal for modular decomposition](https://github.com/open-edge-platform/edge-manageability-framework/blob/main/design-proposals/eim-modular-decomposition.md) calls out a need for exposing both a full set of EMF APIs, and a need for exposing only a subset of APIs as required by individual workflows taking advantage of a modular architecture. This proposal will explore, how the APIs can be decomposed and how the decomposed output can be used as version of API service module. + +## Background and Context + +In EMF 2025.2 the API service is deployed via a helm chart deployed by Argo CD. The API service is run and deployed in a container kick-started from the API service container image. The API is build using the OpenAPI spec. There are multiple levels of APIs currently available with individual specs available for each domain in [orch-utils](https://github.com/open-edge-platform/orch-utils/tree/main/tenancy-api-mapping/openapispecs/generated) + +The list of domain APIs include: + +- Catalog and Catalog utilities APIs +- App deployment manager and app resource manager APIs +- Cluster APIs +- EIM APIs +- Alert Monitoring APIs +- MPS and RPS APIs +- Metadata broker and Tenancy APIs + +There are two levels to the API decomposition + +- Decomposition of above domain levels +- Decomposition within domain (ie. separation at EIM domain level, where overall set of APIs includes onboarding/provisioning/day2 APIs but another workflow may support only onboarding/provisioning without day2 support ) + +The following questions must be answered and investigated: + +- How the API service is build currently + - It is build from a proto definition and code is autogenerated by "buf" tool - [See How NB API is Currently Built](#how-nb-api-is-currently-built) +- How the API service container image is build currently +- How the API service helm charts are build currently +- What level of decomposition is needed from the required workflows +- How to decomposition API at domain level + - At domain level the APIs are deployed as separate services +- How to decomposition API within domain level +- How to build various API service version as per desired workflows using the modular APIs +- How to deliver the various API service versions as per desired workflows +- How to expose the list of available APIs for client consumption (orch-cli) + +Uncertainties: + +- How does potential removal of the API gateway affect the exposing of the APIs to the client +- How will the decomposition and availability of APIs within the API service be mapped back to the Inventory and the set of SB APIs. + +### Decomposing the release of API service as a module + +Once the investigation is completed on how the API service is created today decisions must be done on how the service will be build and released as a module. + +- The build of the API service itself will depend on the results of "top to bottom" and "bottom to top" decomposition investigations. +- The individual versions of API service can be packaged as versioned container images: + - apiv2-emf:x.x.x + - apiv2-workflow1:x.x.x + - apiv2-workflow2:x.x.x +- Alternatively if the decomposition does not result in multiple version of the API service the service could be released as same docker image but managed by flags provided to container that alter the behaviour of the API service in runtime. +- The API service itself should still be packaged for deployment as a helmchart regardless of deployment via ArgoCD or other medium/technique. Decision should be made if common helmchart is used with override values for container image and other related values (preferred) or individual helmcharts need to be released. + +### Decomposing the API service + +An investigation needs to be conducted into how the API service can be decomposed to be rebuilt as various flavours of same API service providing different set of APIs. + +- Preferably the total set of APIs serves as the main source of the API service, and other flavours/subsets are automatically derived from this based on the required functionality. Making the maintenance of the API simple and in one place. +- The APIs service should be decomposed at the domain level meaning that all domains or subset of domains should be available as part of the EMF - they are already decomposed/modular at this level and deployed as separate services. +- The APIs service should be decomposed within the domain level meaning that only subset of the available APIs may need to be released and/or exposed at API service level. As an example within the EIM domain we may not want to expose the Day 2 functionality for some workflows which currently are part of the EIM OpenAPI spec. +- The APIs service may also need to be decomposed at individual internal service level ie host resource may need to ha different data model across use cases. + +The following are the usual options to decomposing or exposing subsets of APIs. + +- ~~API Gateway that would only expose certain endpoints to user~~ - this is a no go for us as we plan to remove the existing API Gateway and it does not actually solve the problem of releasing only specific flavours of EMF. +- Maintain multiple OpenAPI specification - while possible to create multiple OpenAPI specs, the maintenance of same APIs across specs will be a large burden - still let's keep this option in consideration in terms of auto generating multiple specs from top spec. +- ~~Authentication & Authorization Based Filtering~~ - this is a no go for us as we do not control the end users of the EMF, and we want to provide tailored modular product for each workflow. +- ~~API Versioning strategy~~ - Creating different API versions for each use-case - too much overhead without benefits similar to maintaining multiple OpenAPI specs. +- ~~Proxy/Middleware Layer~~ - Similar to API Gateway - does not fit our use cases +- OpenAPI Spec Manipulation - This approach uses OpenAPI's extension mechanism (properties starting with x-) to add metadata that describes which audiences, use cases, or clients should have access to specific endpoints, operations, or schemas. This approach is worth investigating to see if it can give us the automated approach for creating individual OpenAPI specs for workflows based on labels. +- Other approach to manipulate how a flavour of OpenAPIs spec can be generated from main spec, or how the API service can be build conditionally using same spec. + +### Consuming the APIs from the CLI + +The best approach would be for the EMF to provide a service/endpoint that will communicate which endpoints/APIs are currently supported by the deployed API service. The CLI would then request that information on login, save the configuration and prevent from using non-supported APIs/commands. The prevention could happen at command call level where a configuration would be checked before a RUNe command is called for a given command. + +## Summary + +1. Assuming that in phase 1 we will retain Traefik for all workflows, we need to check how the Traefik->EIM mapping will behave and needs to behave when EIM only supports subset of APIs, and establish if the set of API calls supported by Treafik API Gateway maps to the supported APIs in EIM API service subset. +2. We need to make sure that our API supports specific usecases and on the other hand it needs to keep compatibility with other workflows - to achieve that, we may need to make code changes in data models. As an example we need to make sure that mandatory fields are supported accordingly across usecases ie. instance creation will require OSprofile for general usecase, but this may not be true for self installed OSes/Edge Nodes. Collaboration with teams/ADR owners is needed to establish what changes are needed at Resource Manager/Inventory levels to accommodate workflows and how will the changes impact the APIs. +3. We need to understand all the scenarios and required services to be supported. And define the APIs per scenario. + +## How NB API is Currently Built + +Currently, apiv2 (infra-core repository) holds definition of REST API services in protocol buffer files (.proto) and uses protoc-gen-connect-openapi to autogenerate the openapi spec - openapi.yaml . + +The input to protoc-gen-connect-openapi comes from: +api/proto/services directory - one file (services.yaml) containing API pperations on all the available resources (Service Layer). +api/proto/resources directory - multiple files with data models - separate file with data model per single inventory resource. + +Protoc-gen-connect-openapi is the tool that is indirectly used to build the openapi spec. It is configured as a plugin within buf (buf.gen.yaml). + +### What is Buf + +Buf is a replacement for protoc (the standard Protocol Buffers compiler). It makes working with .proto files easier as it replaces messy protoc commands with clean config file. It is a all-in-one tool as it provides compiling, linting, breaking change detection, and dependency management. + +In infra-core/apiv2, "buf generate" command is executed within the "make generate" or "make buf-gen" target to generate the OpenAPI 3.0 spec directly from .proto files in api/proto/ directory. + +The following is the current, full buf configuration (buf.gen.yaml): + +```yaml +plugins: + # go - https://pkg.go.dev/google.golang.org/protobuf + - name: go + out: internal/pbapi + opt: + - paths=source_relative + + # go grpc - https://pkg.go.dev/google.golang.org/grpc + - name: go-grpc + out: internal/pbapi + opt: + - paths=source_relative + - require_unimplemented_servers=false + + # go install github.com/sudorandom/protoc-gen-connect-openapi@v0.17.0 + - name: connect-openapi + path: protoc-gen-connect-openapi + out: api/openapi + strategy: all + opt: + - format=yaml + - short-service-tags + - short-operation-ids + - path=openapi.yaml + + # grpc-gateway - https://grpc-ecosystem.github.io/grpc-gateway/ + - name: grpc-gateway + out: internal/pbapi + opt: + - paths=source_relative + + # docs - https://github.com/pseudomuto/protoc-gen-doc + - plugin: doc + out: docs + opt: markdown,proto.md + strategy: all + + - plugin: go-const + out: internal/pbapi + path: ["go", "run", "./cmd/protoc-gen-go-const"] + opt: + - paths=source_relative +``` + +Protoc-gen-connect-openapi plugin takes as an input one full openapi spec that includes all services (services.proto) and outputs the openapi spec in api/openapi. + +Key Items: +- Input: api/proto/**/*.proto +- Config: buf.gen.yaml, buf.work.yaml, buf.yaml +- Output: openapi.yaml +- Tool: protoc-gen-connect-openapi + +Based on the content of api/proto/ , buf also generates: +- the Go code ( Go structs, gRPC clients/services) in internal/pbapi +- gRPC gateway: REST to gRPC proxy code - HTTP handlers that proxy REST calls to gRPC (in internal/pbapi/**/*.pb.gw.go ) +- documentation: docs/proto.md + +## Building REST API Spec per Scenario + +The following is the proposed solution (draft) to the requirement for decomposistion of EMF, where the exposed REST API is limited to support specific scenario and maintains comatibility with other scenarios. + +1. Split services.yaml file into multiple folders/files per service. +2. Maintain a manifest that lists names of REST API services suported by scenario. +3. Expose a new endpoint that list supported services in current scenario. +4. Change "buf-gen" make target to process only services used by the scenario, by using additional parameter "path", list of services need to come from the manifest in step 2). Example to use service1 and service2 services: + +```bash +bug generate --path api/proto/services/service1/v1 api/proto/services/service2/v1 +``` + +5. Step 4 generated the openapi spec openapi.yaml only for the services supported by particular scenario. +6. CLI is built based on the full REST API spec (also built earlier), but gets the list of supported services from the new API andpoint (step 3) and adjust its internal logic so it calls only supported REST API services/endpoints. When simple curl calls are used to unsupported endpoints, - default message about unsupported service is returned. +