Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions rest-api/api/pkg/api/handler/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/NVIDIA/infra-controller/rest-api/db/pkg/db/paginator"
cdbp "github.com/NVIDIA/infra-controller/rest-api/db/pkg/db/paginator"
swe "github.com/NVIDIA/infra-controller/rest-api/site-workflow/pkg/error"
wutil "github.com/NVIDIA/infra-controller/rest-api/workflow/pkg/util"
"github.com/labstack/echo/v4"

"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -435,9 +434,7 @@ func (cvh CreateVPCHandler) Handle(c echo.Context) error {
statusDetails := []cdbm.StatusDetail{*ssd}

// Make a best-effort attempt to return a response with the allocated VNI.
if controllerVpc.GetStatus() != nil {
activeVni := wutil.GetUint32PtrToIntPtr(controllerVpc.GetStatus().Vni)

if activeVni := cdbm.VpcProtoAllocatedVNI(controllerVpc); activeVni != nil {
uvpcInput := cdbm.VpcUpdateInput{
VpcID: vpc.ID,
ActiveVni: activeVni,
Expand Down
63 changes: 63 additions & 0 deletions rest-api/api/pkg/api/model/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,63 @@ func (avvur APIVpcVirtualizationUpdateRequest) Validate(existingVpc *cdbm.Vpc) e
return nil
}

// APIVpcConfig captures the desired configuration for a VPC.
type APIVpcConfig struct {
// Name is the name of the VPC
Name string `json:"name"`
// Description is the description of the VPC
Description *string `json:"description"`
// SiteID is the ID of the Site
SiteID *string `json:"siteId"`
// NetworkVirtualizationType is a VPC virtualization type
NetworkVirtualizationType *string `json:"networkVirtualizationType"`
// RoutingProfile is the applied routing profile for the VPC, when known.
RoutingProfile *string `json:"routingProfile"`
// RequestedVni is the explicitly requested VPC VNI at creation time _if_ one was requested.
RequestedVni *int `json:"requestedVni"`
// NetworkSecurityGroupID is the ID of attached NSG, if any
NetworkSecurityGroupID *string `json:"networkSecurityGroupId"`
// NVLinkLogicalPartitionID is the ID of the NVLinkLogicalPartition
NVLinkLogicalPartitionID *string `json:"nvLinkLogicalPartitionId"`
// Labels is VPC labels specified by user
Labels map[string]string `json:"labels"`
}

// APIVpcRuntimeStatus captures observed runtime status for a VPC.
type APIVpcRuntimeStatus struct {
// Vni is the active/actual VNI of the VPC, regardless of whether it was
// explicitly requested or auto-allocated.
Vni *int `json:"vni"`
}

func newAPIVpcConfig(dbVpc cdbm.Vpc) APIVpcConfig {
cfg := APIVpcConfig{
Name: dbVpc.Name,
Description: dbVpc.Description,
SiteID: util.GetUUIDPtrToStrPtr(&dbVpc.SiteID),
Labels: dbVpc.Labels,
NetworkSecurityGroupID: dbVpc.NetworkSecurityGroupID,
RequestedVni: dbVpc.Vni,
}
if dbVpc.NetworkVirtualizationType != nil {
cfg.NetworkVirtualizationType = dbVpc.NetworkVirtualizationType
}
if dbVpc.RoutingProfile != nil {
routingProfile := normalizeAPIVpcRoutingProfileFromSite(*dbVpc.RoutingProfile)
cfg.RoutingProfile = &routingProfile
}
if dbVpc.NVLinkLogicalPartitionID != nil {
cfg.NVLinkLogicalPartitionID = util.GetUUIDPtrToStrPtr(dbVpc.NVLinkLogicalPartitionID)
}
return cfg
}

func newAPIVpcRuntimeStatus(dbVpc cdbm.Vpc) APIVpcRuntimeStatus {
return APIVpcRuntimeStatus{
Vni: dbVpc.ActiveVni,
}
}

// APIVpc is a data structure to capture information about VPC at the API layer
type APIVpc struct {
// ID is the unique UUID v4 identifier of the VPC in NICo Cloud
Expand Down Expand Up @@ -336,6 +393,10 @@ type APIVpc struct {
Status string `json:"status"`
// StatusHistory is the status detail records for the VPC over time
StatusHistory []APIStatusDetail `json:"statusHistory"`
// Config is the desired configuration for the VPC
Config APIVpcConfig `json:"config"`
// RuntimeStatus is the observed runtime status for the VPC
RuntimeStatus APIVpcRuntimeStatus `json:"runtimeStatus"`
// CreatedAt indicates the ISO datetime string for when the entity was created
Created time.Time `json:"created"`
// Updated indicates the ISO datetime string for when the VPC was last updated
Expand All @@ -360,6 +421,8 @@ func NewAPIVpc(dbVpc cdbm.Vpc, dbsds []cdbm.StatusDetail) APIVpc {
Updated: dbVpc.Updated,
RequestedVni: dbVpc.Vni,
Vni: dbVpc.ActiveVni,
Config: newAPIVpcConfig(dbVpc),
RuntimeStatus: newAPIVpcRuntimeStatus(dbVpc),
}

if dbVpc.NetworkVirtualizationType != nil {
Expand Down
10 changes: 10 additions & 0 deletions rest-api/api/pkg/api/model/vpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,16 @@ func TestNewAPIVpc(t *testing.T) {
assert.Equal(t, tt.want.StatusHistory, got.StatusHistory)
assert.Equal(t, tt.want.Created, got.Created)
assert.Equal(t, tt.want.Updated, got.Updated)

assert.Equal(t, tt.want.Name, got.Config.Name)
assert.Equal(t, tt.want.Description, got.Config.Description)
assert.Equal(t, tt.want.SiteID, got.Config.SiteID)
assert.Equal(t, tt.want.NetworkVirtualizationType, got.Config.NetworkVirtualizationType)
assert.Equal(t, tt.want.RoutingProfile, got.Config.RoutingProfile)
assert.Equal(t, tt.want.RequestedVni, got.Config.RequestedVni)
assert.Equal(t, tt.want.NetworkSecurityGroupID, got.Config.NetworkSecurityGroupID)
assert.Equal(t, tt.want.Labels, got.Config.Labels)
assert.Equal(t, tt.want.Vni, got.RuntimeStatus.Vni)
})
}
}
Expand Down
235 changes: 208 additions & 27 deletions rest-api/db/pkg/db/model/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,36 +181,179 @@ func (vpc *Vpc) toMetadataProto() *corev1.Metadata {
return md
}

func vpcStringToProtoVirtualizationType(virtType *string) *corev1.VpcVirtualizationType {
if virtType == nil {
return nil
}
nwvt := corev1.VpcVirtualizationType_ETHERNET_VIRTUALIZER
switch *virtType {
case corev1.VpcVirtualizationType_FNN.String():
nwvt = corev1.VpcVirtualizationType_FNN
case corev1.VpcVirtualizationType_FLAT.String():
nwvt = corev1.VpcVirtualizationType_FLAT
}
return &nwvt
}

func vpcProtoVirtualizationTypeToString(virtType corev1.VpcVirtualizationType) string {
switch virtType {
case corev1.VpcVirtualizationType_FNN:
return VpcFNN
case corev1.VpcVirtualizationType_FLAT:
return VpcFlat
default:
return VpcEthernetVirtualizer
}
}

func intPtrToUint32Ptr(v *int) *uint32 {
if v == nil {
return nil
}
u := uint32(*v)
return &u
}

func uint32PtrToIntPtr(v *uint32) *int {
if v == nil {
return nil
}
i := int(*v)
return &i
}

// VpcProtoConfig returns the canonical desired configuration from a
// site-reported Vpc proto, falling back to deprecated flat fields when
// config is unset.
func VpcProtoConfig(proto *corev1.Vpc) *corev1.VpcConfig {
if proto == nil {
return nil
}
if proto.Config != nil {
return proto.Config
}
cfg := &corev1.VpcConfig{
TenantOrganizationId: proto.TenantOrganizationId,
TenantKeysetId: proto.TenantKeysetId,
NetworkSecurityGroupId: proto.NetworkSecurityGroupId,
Vni: proto.Vni,
RoutingProfileType: proto.RoutingProfileType,
}
if proto.NetworkVirtualizationType != nil {
cfg.NetworkVirtualizationType = proto.NetworkVirtualizationType
}
if proto.DefaultNvlinkLogicalPartitionId != nil {
cfg.DefaultNvlinkLogicalPartitionId = proto.DefaultNvlinkLogicalPartitionId
}
return cfg
}

// VpcProtoAllocatedVNI returns the active VNI from a site-reported Vpc
// proto, preferring status.vni and falling back to deprecated_vni.
func VpcProtoAllocatedVNI(proto *corev1.Vpc) *int {
if proto == nil {
return nil
}
if proto.Status != nil && proto.Status.Vni != nil {
return uint32PtrToIntPtr(proto.Status.Vni)
}
if proto.DeprecatedVni != nil {
return uint32PtrToIntPtr(proto.DeprecatedVni)
}
return nil
}

// VpcProtoNetworkVirtualizationType returns the network virtualization
// type string from a site-reported Vpc proto, preferring config and
// falling back to the deprecated flat field.
func VpcProtoNetworkVirtualizationType(proto *corev1.Vpc) *string {
cfg := VpcProtoConfig(proto)
if cfg != nil && cfg.NetworkVirtualizationType != nil {
s := vpcProtoVirtualizationTypeToString(*cfg.NetworkVirtualizationType)
return &s
}
if proto != nil && proto.NetworkVirtualizationType != nil {
s := vpcProtoVirtualizationTypeToString(*proto.NetworkVirtualizationType)
return &s
}
return nil
}

// VpcProtoRoutingProfile returns the routing profile from a
// site-reported Vpc proto, preferring config and falling back to the
// deprecated flat field.
func VpcProtoRoutingProfile(proto *corev1.Vpc) *string {
cfg := VpcProtoConfig(proto)
if cfg != nil && cfg.RoutingProfileType != nil {
return cfg.RoutingProfileType
}
if proto != nil {
return proto.RoutingProfileType
}
return nil
}

// VpcProtoNetworkSecurityGroupID returns the NSG ID from a site-reported
// Vpc proto, preferring config and falling back to the deprecated flat
// field.
func VpcProtoNetworkSecurityGroupID(proto *corev1.Vpc) *string {
cfg := VpcProtoConfig(proto)
if cfg != nil {
return cfg.NetworkSecurityGroupId
}
if proto != nil {
return proto.NetworkSecurityGroupId
}
return nil
}

// ToProto converts this VPC into its workflow proto representation.
// Used as the canonical entity-to-proto conversion; request-shape
// protos (create / update) are produced by `ToProto` methods on the
// corresponding API request types in api/pkg/api/model/vpc.go.
//
// `NetworkVirtualizationType` is mapped from the DB column's string
// value to the workflow enum (defaulting to `ETHERNET_VIRTUALIZER`
// when the string is set but unrecognized, matching the pre-refactor
// handler behaviour). It is omitted from the proto when the DB
// column is nil.
// Populates structured `config` and `status` as canonical fields and
// mirrors deprecated flat fields for backward compatibility with older
// site agents and clients.
func (vpc *Vpc) ToProto() *corev1.Vpc {
proto := &corev1.Vpc{
Id: &corev1.VpcId{Value: vpc.GetSiteID().String()},
Name: vpc.Name,
TenantOrganizationId: vpc.Org,
NetworkSecurityGroupId: vpc.NetworkSecurityGroupID,
Metadata: vpc.toMetadataProto(),
}
metadata := vpc.toMetadataProto()
virtType := vpcStringToProtoVirtualizationType(vpc.NetworkVirtualizationType)
desiredVni := intPtrToUint32Ptr(vpc.Vni)
allocatedVni := intPtrToUint32Ptr(vpc.ActiveVni)

var nvllpProto *corev1.NVLinkLogicalPartitionId
if vpc.NVLinkLogicalPartitionID != nil {
proto.DefaultNvlinkLogicalPartitionId = &corev1.NVLinkLogicalPartitionId{Value: vpc.NVLinkLogicalPartitionID.String()}
}
if vpc.NetworkVirtualizationType != nil {
nwvt := corev1.VpcVirtualizationType_ETHERNET_VIRTUALIZER
switch *vpc.NetworkVirtualizationType {
case corev1.VpcVirtualizationType_FNN.String():
nwvt = corev1.VpcVirtualizationType_FNN
case corev1.VpcVirtualizationType_FLAT.String():
nwvt = corev1.VpcVirtualizationType_FLAT
}
proto.NetworkVirtualizationType = &nwvt
nvllpProto = &corev1.NVLinkLogicalPartitionId{Value: vpc.NVLinkLogicalPartitionID.String()}
}

config := &corev1.VpcConfig{
TenantOrganizationId: vpc.Org,
NetworkSecurityGroupId: vpc.NetworkSecurityGroupID,
DefaultNvlinkLogicalPartitionId: nvllpProto,
Vni: desiredVni,
RoutingProfileType: vpc.RoutingProfile,
}
if virtType != nil {
config.NetworkVirtualizationType = virtType
}

proto := &corev1.Vpc{
Id: &corev1.VpcId{Value: vpc.GetSiteID().String()},
Metadata: metadata,
Config: config,
Name: vpc.Name,
TenantOrganizationId: vpc.Org,
NetworkSecurityGroupId: vpc.NetworkSecurityGroupID,
DefaultNvlinkLogicalPartitionId: nvllpProto,
Vni: desiredVni,
DeprecatedVni: allocatedVni,
RoutingProfileType: vpc.RoutingProfile,
}
if virtType != nil {
proto.NetworkVirtualizationType = virtType
}
if allocatedVni != nil {
proto.Status = &corev1.VpcStatus{Vni: allocatedVni}
}
return proto
}
Expand All @@ -228,6 +371,8 @@ func (vpc *Vpc) ToProto() *corev1.Vpc {
// - `Name` is sourced from `proto.Metadata.Name` when set, falling
// back to the (deprecated) top-level `proto.Name` so the method
// keeps working through the deprecation window.
// - Config fields are preferred over deprecated flat mirrors when
// both are present.
// - Optional pointer fields (NetworkSecurityGroupID,
// NVLinkLogicalPartitionID) are cleared when the proto omits them
// OR when the proto value is invalid (e.g. an unparseable UUID).
Expand All @@ -246,17 +391,53 @@ func (vpc *Vpc) FromProto(proto *corev1.Vpc) {
if proto.Metadata != nil && proto.Metadata.Name != "" {
vpc.Name = proto.Metadata.Name
}
vpc.Org = proto.TenantOrganizationId
vpc.NetworkSecurityGroupID = proto.NetworkSecurityGroupId
if proto.DefaultNvlinkLogicalPartitionId != nil {
if id, err := uuid.Parse(proto.DefaultNvlinkLogicalPartitionId.Value); err == nil {

cfg := VpcProtoConfig(proto)
if cfg != nil && cfg.TenantOrganizationId != "" {
vpc.Org = cfg.TenantOrganizationId
} else {
vpc.Org = proto.TenantOrganizationId
}

nsgID := VpcProtoNetworkSecurityGroupID(proto)
vpc.NetworkSecurityGroupID = nsgID

var nvllpProto *corev1.NVLinkLogicalPartitionId
if cfg != nil {
nvllpProto = cfg.DefaultNvlinkLogicalPartitionId
}
if nvllpProto == nil {
nvllpProto = proto.DefaultNvlinkLogicalPartitionId
}
if nvllpProto != nil {
if id, err := uuid.Parse(nvllpProto.Value); err == nil {
vpc.NVLinkLogicalPartitionID = &id
} else {
vpc.NVLinkLogicalPartitionID = nil
}
} else {
vpc.NVLinkLogicalPartitionID = nil
}

if virtType := VpcProtoNetworkVirtualizationType(proto); virtType != nil {
vpc.NetworkVirtualizationType = virtType
} else {
vpc.NetworkVirtualizationType = nil
}

if routingProfile := VpcProtoRoutingProfile(proto); routingProfile != nil {
vpc.RoutingProfile = routingProfile
} else {
vpc.RoutingProfile = nil
}

if cfg != nil && cfg.Vni != nil {
vpc.Vni = uint32PtrToIntPtr(cfg.Vni)
} else {
vpc.Vni = uint32PtrToIntPtr(proto.Vni)
}
vpc.ActiveVni = VpcProtoAllocatedVNI(proto)

if proto.Metadata != nil {
vpc.Description = nil
if proto.Metadata.Description != "" {
Expand Down
Loading
Loading