Skip to content
Merged
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
7 changes: 5 additions & 2 deletions rest-api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ core-protogen: .tools-stamp
echo "Generating protobuf for Core"
cd proto/core && PATH="$(TOOLS_DIR):$$PATH" $(BUF) generate
go fmt ./...
python3 scripts/check_source_headers.py --fix
python3 scripts/check_source_headers.py --fix \
--include-untracked 'proto/core/gen/v1/*.pb.go'

flow-proto:
$(MAKE) flow-proto-clean
Expand Down Expand Up @@ -893,7 +894,9 @@ generate-sdk: $(OPENAPI_GENERATOR_JAR)
--additional-properties=isGoSubmodule=true,enumClassPrefix=true \
--global-property=apis,models,supportingFiles
rm -rf sdk/standard/docs sdk/standard/api sdk/standard/README.md sdk/standard/test sdk/standard/.openapi-generator
python3 scripts/check_source_headers.py --fix
python3 scripts/check_source_headers.py --fix \
--include-untracked 'sdk/standard/api_*.go' \
--include-untracked 'sdk/standard/model_*.go'
@echo "Client generated in sdk/standard/"
cd sdk/standard && go build ./...
@echo "Client compiles successfully"
Expand Down
150 changes: 119 additions & 31 deletions rest-api/api/pkg/api/handler/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,16 +234,34 @@ func (cvh CreateVPCHandler) Handle(c echo.Context) error {
}
}

var routingProfile *string
if apiRequest.RoutingProfile != nil {
// For now, we gate on TargetedInstanceCreation permission,
// which must be effective for the VPC's Site.
enabledForSite, derr := common.TenantHasTargetedInstanceCreation(ctx, nil, cvh.dbSession, tenant, &common.TenantPrivilegeScope{SiteID: &site.ID})
if derr != nil {
logger.Error().Err(derr).Msg("error checking effective targeted instance creation for Site")
// TargetedInstanceCreation supplies both policies, but keep write authorization
// separate from effective-profile response visibility.
tenantCanSetRoutingProfile := false
tenantCanViewEffectiveRoutingProfile := false
if cdbm.VpcTypeSupportsRoutingProfile(networkVirtualizationType) || apiRequest.RoutingProfile != nil {
tenantHasTargetedInstanceCreation, err := common.TenantHasTargetedInstanceCreation(ctx, nil, cvh.dbSession, tenant, &common.TenantPrivilegeScope{SiteID: &site.ID})
if err != nil {
logger.Error().Err(err).Msg("error resolving TargetedInstanceCreation for Tenant/Site")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to verify privileges for Site", nil)
}
if !enabledForSite {
tenantCanSetRoutingProfile = tenantHasTargetedInstanceCreation
tenantCanViewEffectiveRoutingProfile = tenantHasTargetedInstanceCreation
}

if apiRequest.RoutingProfileOverrides != nil && !cdbm.VpcTypeSupportsRoutingProfile(networkVirtualizationType) {
logger.Warn().Str("networkVirtualizationType", *networkVirtualizationType).Msg("routing profile overrides are not supported for network virtualization type")
return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, fmt.Sprintf("Routing profile overrides are not supported for network virtualization type: %s", *networkVirtualizationType), nil)
}

if apiRequest.RoutingProfileOverrides != nil && !tenantCanSetRoutingProfile {
logger.Warn().Msg("tenant does not have sufficient privileges to set `routingProfileOverrides`")
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Tenant does not have sufficient privileges to set `routingProfileOverrides`", nil)
}

var routingProfile *string
if apiRequest.RoutingProfile != nil {
// Routing-profile writes require TargetedInstanceCreation for the VPC's Site.
if !tenantCanSetRoutingProfile {
logger.Warn().Msg("tenant does not have sufficient privileges to set `routingProfile`")
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Tenant does not have sufficient privileges to set `routingProfile`", nil)
}
Expand Down Expand Up @@ -332,6 +350,7 @@ func (cvh CreateVPCHandler) Handle(c echo.Context) error {
SiteID: site.ID,
NetworkVirtualizationType: networkVirtualizationType,
RoutingProfile: routingProfile,
RoutingProfileOverrides: apiRequest.RoutingProfileOverrides.ToDB(),
NVLinkLogicalPartitionID: defaultNvllPartitionId,
Labels: labels,
Status: cdbm.VpcStatusProvisioning,
Expand Down Expand Up @@ -438,15 +457,20 @@ 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.
// Make a best-effort attempt to cache the controller-reported VNI and
// effective routing profile for the response.
controllerVpcModel := &cdbm.Vpc{}
controllerVpcModel.FromProto(controllerVpc)
activeVni := controllerVpcModel.ActiveVni
if activeVni != nil {
effectiveRoutingProfile := controllerVpcModel.EffectiveRoutingProfile
if activeVni != nil || effectiveRoutingProfile != nil {
uvpcInput := cdbm.VpcUpdateInput{
VpcID: vpc.ID,
ActiveVni: activeVni,
Status: cutil.GetPtr(cdbm.VpcStatusReady),
VpcID: vpc.ID,
ActiveVni: activeVni,
EffectiveRoutingProfile: effectiveRoutingProfile,
}
if activeVni != nil {
uvpcInput.Status = cutil.GetPtr(cdbm.VpcStatusReady)
}
updatedVpc, err := vpcDAO.Update(ctx, nil, uvpcInput)
if err != nil {
Expand All @@ -455,20 +479,22 @@ func (cvh CreateVPCHandler) Handle(c echo.Context) error {
// Update the vpc being returned if all went well.
vpc = updatedVpc

// Best effort create status detail
ssd, err = sdDAO.Create(ctx, nil, cdbm.StatusDetailCreateInput{EntityID: vpc.ID.String(), Status: cdbm.VpcStatusReady, Message: cutil.GetPtr("VPC is ready for use")})
if err != nil {
logger.Error().Err(err).Msg("error creating Status Detail DB entry")
} else if ssd == nil {
logger.Error().Err(err).Msg("unexpected nil Status Detail returned from DB")
} else {
statusDetails = append(statusDetails, *ssd)
if activeVni != nil {
// Best effort create status detail
ssd, err = sdDAO.Create(ctx, nil, cdbm.StatusDetailCreateInput{EntityID: vpc.ID.String(), Status: cdbm.VpcStatusReady, Message: cutil.GetPtr("VPC is ready for use")})
if err != nil {
logger.Error().Err(err).Msg("error creating Status Detail DB entry")
} else if ssd == nil {
logger.Error().Err(err).Msg("unexpected nil Status Detail returned from DB")
} else {
statusDetails = append(statusDetails, *ssd)
}
}
}
}

// Create response
apiVpc := model.NewAPIVpc(*vpc, statusDetails)
apiVpc := model.NewAPIVpc(*vpc, statusDetails, tenantCanViewEffectiveRoutingProfile)

logger.Info().Msg("finishing API handler")

Expand Down Expand Up @@ -588,6 +614,34 @@ func (uvh UpdateVPCHandler) Handle(c echo.Context) error {
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "VPC does not belong to current Tenant", nil)
}

// TargetedInstanceCreation supplies both policies, but keep write authorization
// separate from effective-profile response visibility.
tenantCanSetRoutingProfile := false
tenantCanViewEffectiveRoutingProfile := false
if cdbm.VpcTypeSupportsRoutingProfile(vpc.NetworkVirtualizationType) {
tenantHasTargetedInstanceCreation, err := common.TenantHasTargetedInstanceCreation(ctx, nil, uvh.dbSession, tenant, &common.TenantPrivilegeScope{SiteID: &vpc.SiteID})
if err != nil {
logger.Error().Err(err).Msg("error resolving TargetedInstanceCreation for Tenant/Site")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to resolve Tenant capability, DB error", nil)
}
tenantCanSetRoutingProfile = tenantHasTargetedInstanceCreation
tenantCanViewEffectiveRoutingProfile = tenantHasTargetedInstanceCreation
}

if apiRequest.RoutingProfileOverrides != nil && !cdbm.VpcTypeSupportsRoutingProfile(vpc.NetworkVirtualizationType) {
networkVirtualizationType := "<unspecified>"
if vpc.NetworkVirtualizationType != nil {
networkVirtualizationType = *vpc.NetworkVirtualizationType
}
logger.Warn().Str("networkVirtualizationType", networkVirtualizationType).Msg("routing profile overrides are not supported for network virtualization type")
return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, fmt.Sprintf("Routing profile overrides are not supported for network virtualization type: %s", networkVirtualizationType), nil)
}

if apiRequest.RoutingProfileOverrides != nil && !tenantCanSetRoutingProfile {
logger.Warn().Msg("tenant does not have sufficient privileges to set `routingProfileOverrides`")
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Tenant does not have sufficient privileges to set `routingProfileOverrides`", nil)
}

// Ensure that Tenant has an Allocation with specified Site
aDAO := cdbm.NewAllocationDAO(uvh.dbSession)
allocationFilter := cdbm.AllocationFilterInput{TenantIDs: []uuid.UUID{tenant.ID}, SiteIDs: []uuid.UUID{vpc.SiteID}}
Expand Down Expand Up @@ -739,11 +793,12 @@ func (uvh UpdateVPCHandler) Handle(c echo.Context) error {
err = cdb.WithTx(ctx, uvh.dbSession, func(tx *cdb.Tx) error {
// Update VPC
uvpcInput := cdbm.VpcUpdateInput{
VpcID: vpc.ID,
Name: apiRequest.Name,
Description: apiRequest.Description,
Labels: labels,
NetworkSecurityGroupID: nsgID,
VpcID: vpc.ID,
Name: apiRequest.Name,
Description: apiRequest.Description,
Labels: labels,
NetworkSecurityGroupID: nsgID,
RoutingProfileOverrides: apiRequest.RoutingProfileOverrides.ToDB(),
}

if defaultNvllPartitionId != nil {
Expand Down Expand Up @@ -779,6 +834,12 @@ func (uvh UpdateVPCHandler) Handle(c echo.Context) error {
shouldClear = true
}

// A changed desired definition invalidates the last controller-resolved value.
if apiRequest.RoutingProfileOverrides != nil {
clearInput.EffectiveRoutingProfile = true
shouldClear = true
}

// Clear it in the db if something should be cleared.
if shouldClear {
clearedVpc, derr := vpcDAO.Clear(ctx, tx, clearInput)
Expand Down Expand Up @@ -863,7 +924,7 @@ func (uvh UpdateVPCHandler) Handle(c echo.Context) error {
}

// Create response
apiVpc := model.NewAPIVpc(*vpc, ssds)
apiVpc := model.NewAPIVpc(*vpc, ssds, tenantCanViewEffectiveRoutingProfile)

logger.Info().Msg("finishing API handler")
return c.JSON(http.StatusOK, apiVpc)
Expand Down Expand Up @@ -982,6 +1043,12 @@ func (uvvh UpdateVPCVirtualizationHandler) Handle(c echo.Context) error {
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "VPC does not belong to current Tenant", nil)
}

tenantCanViewEffectiveRoutingProfile, err := common.TenantHasTargetedInstanceCreation(ctx, nil, uvvh.dbSession, tenant, &common.TenantPrivilegeScope{SiteID: &vpc.SiteID})
if err != nil {
logger.Error().Err(err).Msg("error resolving TargetedInstanceCreation for Tenant/Site")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to resolve Tenant capability, DB error", nil)
}

// Ensure that Tenant has access to Site
tsDAO := cdbm.NewTenantSiteDAO(uvvh.dbSession)
_, err = tsDAO.GetByTenantIDAndSiteID(ctx, nil, tenant.ID, vpc.SiteID, nil)
Expand Down Expand Up @@ -1132,7 +1199,7 @@ func (uvvh UpdateVPCVirtualizationHandler) Handle(c echo.Context) error {
}

// Create response
apiVpc := model.NewAPIVpc(*uv, ssds)
apiVpc := model.NewAPIVpc(*uv, ssds, tenantCanViewEffectiveRoutingProfile)

logger.Info().Msg("finishing API handler")
return c.JSON(http.StatusOK, apiVpc)
Expand Down Expand Up @@ -1242,6 +1309,17 @@ func (gvh GetVPCHandler) Handle(c echo.Context) error {
return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "VPC does not belong to current Tenant", nil)
}

tenantCanViewEffectiveRoutingProfile := false
// Existing non-FNN rows can carry cached effective state, so gate that state
// instead of hiding it solely based on VPC type.
if cdbm.VpcTypeSupportsRoutingProfile(vpc.NetworkVirtualizationType) || vpc.EffectiveRoutingProfile != nil {
tenantCanViewEffectiveRoutingProfile, err = common.TenantHasTargetedInstanceCreation(ctx, nil, gvh.dbSession, tenant, &common.TenantPrivilegeScope{SiteID: &vpc.SiteID})
if err != nil {
logger.Error().Err(err).Msg("error resolving TargetedInstanceCreation for Tenant/Site")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to resolve Tenant capability, DB error", nil)
}
}

// Get status details
sdDAO := cdbm.NewStatusDetailDAO(gvh.dbSession)

Expand All @@ -1252,7 +1330,7 @@ func (gvh GetVPCHandler) Handle(c echo.Context) error {
}

// Create response
vc := model.NewAPIVpc(*vpc, ssds)
vc := model.NewAPIVpc(*vpc, ssds, tenantCanViewEffectiveRoutingProfile)

logger.Info().Msg("finishing API handler")

Expand Down Expand Up @@ -1415,6 +1493,16 @@ func (gavh GetAllVPCHandler) Handle(c echo.Context) error {
}
tenant := tenants[0]

privilegedSiteIDs, err := common.GetPrivilegedAccessSiteIDsForTenant(ctx, nil, gavh.dbSession, &tenant)
if err != nil {
logger.Error().Err(err).Msg("error resolving privileged Site access for Tenant")
return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to resolve Tenant capability, DB error", nil)
}
privilegedSites := make(map[uuid.UUID]bool, len(privilegedSiteIDs))
for _, siteID := range privilegedSiteIDs {
privilegedSites[siteID] = true
}

// Get all VPCs by Tenant, and Site, if specified
vpcDAO := cdbm.NewVpcDAO(gavh.dbSession)

Expand Down Expand Up @@ -1531,7 +1619,7 @@ func (gavh GetAllVPCHandler) Handle(c echo.Context) error {
apiVpcs := []model.APIVpc{}

for _, vpc := range vpcs {
apiVpc := model.NewAPIVpc(vpc, ssdMap[vpc.ID.String()])
apiVpc := model.NewAPIVpc(vpc, ssdMap[vpc.ID.String()], privilegedSites[vpc.SiteID])
apiVpcs = append(apiVpcs, apiVpc)
}

Expand Down
Loading
Loading