Calling on the makefile target:
make oapi-codegen-aether-2.1.0 has the effect of first validating that the OpenAPI 3 specifications contains no errors.
It then proceeds to generate Go code that performs the following functions:
- the types from the OpenAPI spec
- the spec (so that the API specification can be retrieved by clients programmatically)
- the paths and methods from the OpenAPI spec
- conversion code to go from OpenAPI types to YANG model types
- conversion code to go from YANG model types to OpenAPI types
- code to call gNMI methods Get() and Set() from REST methods GET, POST, DELETE
- code to integrate with the Echo Web Server built in to this project that serves the API
Breaking the steps down individually:
The first step generates Go type definitions from the Schema definitions in the specification:
oapi-codegen -generate types -package types -o pkg/aether_2_1_0/types/aether-2.1.0-types.go api/aether-2.1.0-openapi3.yamlThe second run bundles up the API specification as a blob in base64, so that it may be extracted programmatically through the REST interface.
oapi-codegen -generate spec -package server -o pkg/aether_2_1_0/server/aether-2.1.0-spec.go api/aether-2.1.0-openapi3.yamlThe third step generates an implementation of handlers for accessing the gNMI client.
type ServerImpl struct {
GnmiClient southbound.GnmiClient
GnmiTimeout time.Duration
}and creates a public and a private method for each of the paths in the specification. e.g.
func (i *ServerImpl) GetApplication(ctx echo.Context, enterpriseId externalRef1.EnterpriseId, applicationId string) errorThese public functions are an implementation of the
ServerInterfaceinterface generated in the next step
func (i *ServerImpl) gnmiGetApplication(ctx context.Context,
openApiPath string, enterpriseId externalRef1.EnterpriseId, args ...string) (*externalRef1.Application, error)The public method is an implementation of a Handler from the 'server' file.
oapi-codegen \
-generate types,server \
-import-mapping imp1:"github.com/onosproject/aether-roc-api/pkg/aether_2_1_0/types",imp2:"github.com/onosproject/aether-models/models/aether-2.1.x/api" \
-package server \
-templates pkg/codegen/templates \
-o pkg/aether_2_1_0/server/aether-2.1.0-impl.go \
api/aether-2.1.0-openapi3.yamlThis call does not use the built in template of
oapi-codegenbut custom templates from the codegen/templates folder.Also note the
-import-mappinghere - it is used to force the set of Go imports to include these paths.Counterintuitively, the prefixes given here e.g.
imp1are discarded by oapi-codegen, and imports are ordered alphabetically, being given the prefixesexternalRef0etc inside the code
To correct an omission of an import, a sed statement is necessary to fix the file after generation.
The
sedstatement here is written with Posix compliance, so that it can run on linux and MacOS.
The fourth step generates the handlers for the Echo Web server.
The RegisterHandlers func gives a list of all the paths and their methods and their respective handler functions on
the serverInterfaceWrapper struct.
These methods pass only an echo.Context argument (as all Echo handlers do) like:
func (w *serverInterfaceWrapper) GetApplication(ctx echo.Context) errorso this function extracts parameters like enterprise-id and application-id from this Context and passes
to another level of function defined in the ServerInterface interface:
// ServerInterface represents all server handlers.
type ServerInterface interface {
// GET /application Container
// (GET /aether/v2.1.x/{enterprise-id}/application/{application-id})
GetApplication(ctx echo.Context, enterpriseId externalRef0.EnterpriseId, applicationId string) error
...It is this interface that the methods in the impl step above implement.
oapi-codegen \
-generate server \
-import-mapping externalRef0:"github.com/onosproject/aether-roc-api/pkg/aether_2_1_0/types" \
-package server \
-templates pkg/codegen/modified \
-o pkg/aether_2_1_0/server/aether-2.1.0-server.go \
api/aether-2.1.0-openapi3.yamlNote that here again the templates in use are customized ones from codegen/modified
The same kind of
sedcommand is used to correct some imports
The fifth step generates code that can transform structs generated from OpenAPI types (from the first steps above),
to structs generated by YGOT from the YANG model, during the aether-models generation stage.
The YGOT structs can then be serialized to JSON and passed in gNMI calls as values.
It creates a function per OpenAPI type - e.g. for Application
func EncodeToGnmiApplication(
jsonObj *types.Application, needKey bool, removeIndex bool, enterpriseId types.EnterpriseId, parentPath string, params ...string) (
[]*gnmi.Update, error) {In the template
pkg/codegen/convert-oapi-gnmi/typedef.tmplthere is a variable$tnbasewhich may have white space padding at the left hand side - the template syntax{{-does not correctly get rid of this leading white space properly unless it is on a new line. To make it distinguishable we add in the marker> !on the new line, so it can be removed easily later withsed.
oapi-codegen \
-generate types \
-import-mapping externalRef0:"github.com/onosproject/aether-models/models/aether-2.1.x/api" \
-package server \
-templates pkg/codegen/convert-oapi-gnmi \
-o pkg/aether_2_1_0/server/aether-2.1.0-convert-oapi-gnmi.go \
api/aether-2.1.0-openapi3.yamlNote that here the templates in use are customized ones from codegen/convert-oapi-gnmi
The
sedcommand used here is to format a particular hack that is used in the templates.Specifically it is removing a
> !inserted in the code generation
The sixth step generates code that can transform structs created from YGOT (from the YANG model) to OpenAPI structs.
The YGOT structs are unmarshalled from the gNMI payloads (in JSON encoding) coming back in to the system in reply to gNMI GetRequests.
The template generates two different kinds of struct.
- with a pointer receiver to a YGOT parent object and
- a second that's a plain function that breaks apart the specific subsection of the YGOT object
An example of 1. is:
func (d *ModelPluginDevice) toApplication(params ...string) (*types.Application, error)and an example of 2. is:
func toApplication(ygotObjValue *reflect.Value, params ...string) (*types.Application, error)Here the generated code relies on utility functions that can walk the Go struct hierarchy e.g.
- utils.FindModelPluginObject
oapi-codegen \
-generate types \
-import-mapping externalRef0:"github.com/onosproject/aether-models/models/aether-2.1.x/api" \
-package server \
-templates pkg/codegen/convert-gnmi-oapi \
-o pkg/aether_2_1_0/server/aether-2.1.0-convert-gnmi-oapi.go \
api/aether-2.1.0-openapi3.yamlNote that here the templates in use are customized ones from codegen/convert-gnmi-oapi
The
sedcommand used here is to format a particular hack that is used in the templates.Specifically it is removing a
> !inserted in the code generation