Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for generic OpenAPI CRUD handling #644

Merged
merged 11 commits into from
Jan 30, 2024
7 changes: 7 additions & 0 deletions .changes/v2.23.0/644-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
* Add internal generic functions to handle CRUD operations for inner and outer entities [GH-644]
* Add section about CRUD functions to `CODING_GUIDELINES.md` [GH-644]
* Convert `DefinedEntityType`, `DefinedEntity`, `DefinedInterface`, `IpSpace`, `IpSpaceUplink`,
`DistributedFirewall`, `DistributedFirewallRule`, `NsxtSegmentProfileTemplate`,
`GetAllIpDiscoveryProfiles`, `GetAllMacDiscoveryProfiles`, `GetAllSpoofGuardProfiles`,
`GetAllQoSProfiles`, `GetAllSegmentSecurityProfiles` to use newly introduced generic CRUD
functions [GH-644]
127 changes: 127 additions & 0 deletions CODING_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,134 @@ the request header.

When the tenant context is not needed (system administration calls), we just pass `nil` as `additionalHeader`.

## Generic CRUD functions for OpenAPI entity implementation

Generic CRUD functions are used to minimize boilerplate for entity implementation in the SDK. They
might not always be the way to go when there are very specific operation needs as it is not worth
having a generic function for single use case. In such cases, low level API client function set,
that is located in `openapi.go` can help to perform such operations.

### Terminology

#### inner vs outer types

For the context of generic CRUD function implementation (mainly in files
`govcd/openapi_generic_outer_entities.go`, `govcd/openapi_generic_inner_entities.go`), such terms
are commonly used:

* `inner` type is the type that is responsible for marshaling/unmarshaling API
request payload and is usually inside `types` package. (e.g. `types.IpSpace`,
`types.NsxtAlbPoolMember`, etc.)
* `outer` (type) - this is the type that wraps `inner` type and possibly any other entities that are
required to perform operations for a particular VCD entity. It will almost always include some
reference to client (`VCDClient` or `Client`), which is required to perform API operations. It may
contain additional fields.

Here are the entities mapped in the example below:

* `DistributedFirewall` is the **`outer`** type
* `types.DistributedFirewallRules` is the **`inner`** type (specified in
`DistributedFirewall.DistributedFirewallRuleContainer` field)
* `client` field contains the client that is required for perfoming API operations
* `VdcGroup` field contains additional data (VDC Group reference) that is required for
implementation of this particular entity

```go
type DistributedFirewall struct {
DistributedFirewallRuleContainer *types.DistributedFirewallRules
client *Client
VdcGroup *VdcGroup
}
```

#### crudConfig

A special type `govcd.crudConfig` is used for passing configuration to both - `inner` and `outer`
generic CRUD functions. It also has an internal `validate()` method, which is called upon execution
of any `inner` and `outer` CRUD functions.

See documentation of `govcd.crudConfig` for the options it provides.

### Use cases

The main consideration when to use which functions depends on whether one is dealing with `inner`
types or `outer` types. Both types can be used for quicker development.

Usually, `outer` type is used for a full featured entity (e.g. `IpSpace`, `NsxtEdgeGateway`), while
`inner` suits cases where one needs to perform operations on an already existing or a read-only
entity.

**Hint:** return value of your entity method will always hint whether it is `inner` or `outer` one:

`inner` type function signature example (returns `*types.VdcNetworkProfile`):

```
func (adminVdc *AdminVdc) UpdateVdcNetworkProfile(vdcNetworkProfileConfig *types.VdcNetworkProfile) (*types.VdcNetworkProfile, error) {
```

`outer` type function signature example (returns `*IpSpace`):

```
func (vcdClient *VCDClient) CreateIpSpace(ipSpaceConfig *types.IpSpace) (*IpSpace, error) {
```

#### inner CRUD functions

The entities that match below criteria are usually going to use `inner` crud functions:
* API property manipulation with separate API endpoints for an already existing entity (e.g. VDC
Network Profiles `Vdc.UpdateVdcNetworkProfile`)
* Read only entities (e.g. NSX-T Segment Profiles `VCDClient.GetAllIpDiscoveryProfiles`)

Inner types are more simple as they can be directly used without any additional overhead. There are
7 functions that can be used:

* `createInnerEntity`
* `updateInnerEntity`
* `updateInnerEntityWithHeaders`
* `getInnerEntity`
* `getInnerEntityWithHeaders`
* `deleteEntityById`
* `getAllInnerEntities`

Existing examples of the implementation are:

* `Vdc.GetVdcNetworkProfile`
* `Vdc.UpdateVdcNetworkProfile`
* `Vdc.DeleteVdcNetworkProfile`
* `VCDClient.GetAllIpDiscoveryProfiles`

#### outer CRUD functions

The entities, that implement complete management of a VCD entity will usually rely on `outer` CRUD
functions. Any `outer` type *must* implement `wrap` method (example signature provided below). It is
required to satisfy generic interface constraint (so that generic functions are able to wrap `inner`
type into `outer` type)

```go
func (o OuterEntity) wrap(inner *InnerEntity) *OuterEntity {
o.OuterEntity = inner
return &o
}
```
There are 5 functions for handling CRU(D).
* `createOuterEntity`
* `updateOuterEntity`
* `getOuterEntity`
* `getOuterEntityWithHeaders`
* `getAllOuterEntities`

*Note*: `D` (deletion) in `CRUD` is a simple operation that does not additionally handle data and
`deleteEntityById` is sufficient.

Existing examples of the implementation are:
* `IpSpace`
* `IpSpaceUplink`
* `DistributedFirewall`
* `DistributedFirewallRule`
* `NsxtSegmentProfileTemplate`
* `DefinedEntityType`
* `DefinedInterface`
* `DefinedEntity`

## Testing

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/vmware/go-vcloud-director/v2

go 1.19
go 1.21

require (
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
Expand Down
5 changes: 0 additions & 5 deletions govcd/adminorg_ldap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package govcd

import (
"fmt"
"time"

"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
Expand Down Expand Up @@ -62,10 +61,6 @@ func (vcd *TestVCD) Test_LDAP(check *C) {
check.Assert(err, IsNil)
}()

sleepTime := 10 * time.Second
fmt.Printf("# Sleeping %s to prevent 'LDAP context not initialized' errors\n", sleepTime.String())
time.Sleep(sleepTime)

// Run tests requiring LDAP from here.
vcd.test_GroupCRUD(check)
vcd.test_GroupFinderGetGenericEntity(check)
Expand Down
Loading
Loading