Skip to content

Commit

Permalink
API: Add all projects support for network zones (from Incus) (#14585)
Browse files Browse the repository at this point in the history
This PR adds support for fetching network zones across all projects.
  • Loading branch information
tomponline authored Jan 10, 2025
2 parents c12546f + 85b129b commit 1527bbf
Show file tree
Hide file tree
Showing 50 changed files with 3,575 additions and 3,286 deletions.
1 change: 1 addition & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ type InstanceServer interface {
GetNetworkAllocations(allProjects bool) (allocations []api.NetworkAllocations, err error)

// Network zone functions ("network_dns" API extension)
GetNetworkZonesAllProjects() (zones []api.NetworkZone, err error)
GetNetworkZoneNames() (names []string, err error)
GetNetworkZones() (zones []api.NetworkZone, err error)
GetNetworkZone(name string) (zone *api.NetworkZone, ETag string, err error)
Expand Down
18 changes: 18 additions & 0 deletions client/lxd_network_zones.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ func (r *ProtocolLXD) GetNetworkZones() ([]api.NetworkZone, error) {
return zones, nil
}

// GetNetworkZonesAllProjects returns a list of network zones across all projects as NetworkZone structs.
func (r *ProtocolLXD) GetNetworkZonesAllProjects() ([]api.NetworkZone, error) {
err := r.CheckExtension("network_zones_all_projects")
if err != nil {
return nil, err
}

zones := []api.NetworkZone{}

u := api.NewURL().Path("network-zones").WithQuery("recursion", "1").WithQuery("all-projects", "true")
_, err = r.queryStruct("GET", u.String(), nil, "", &zones)
if err != nil {
return nil, err
}

return zones, nil
}

// GetNetworkZone returns a Network zone entry for the provided name.
func (r *ProtocolLXD) GetNetworkZone(name string) (*api.NetworkZone, string, error) {
err := r.CheckExtension("network_dns")
Expand Down
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2542,3 +2542,7 @@ This introduces the configuration keys {config:option}`storage-ceph-pool-conf:ce
## `network_get_target`

Adds optional `target` parameter to `GET /1.0/network`. When target is set, forward the request to the specified cluster member and return the non-managed interfaces from that member.

## `network_zones_all_projects`

This adds support for listing network zones across all projects using the `all-projects` parameter in `GET /1.0/network-zones` requests.
15 changes: 15 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3899,6 +3899,11 @@ definitions:
example: example.net
type: string
x-go-name: Name
project:
description: Project name
example: project1
type: string
x-go-name: Project
used_by:
description: List of URLs of objects using this network zone
example:
Expand Down Expand Up @@ -12273,6 +12278,11 @@ paths:
in: query
name: project
type: string
- description: Retrieve network zones from all projects
example: true
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down Expand Up @@ -12732,6 +12742,11 @@ paths:
in: query
name: project
type: string
- description: Retrieve network zones from all projects
example: true
in: query
name: all-projects
type: boolean
produces:
- application/json
responses:
Expand Down
26 changes: 22 additions & 4 deletions lxc/network_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ type cmdNetworkZoneList struct {
global *cmdGlobal
networkZone *cmdNetworkZone

flagFormat string
flagFormat string
flagAllProjects bool
}

func (c *cmdNetworkZoneList) command() *cobra.Command {
Expand All @@ -87,6 +88,7 @@ func (c *cmdNetworkZoneList) command() *cobra.Command {

cmd.RunE = c.run
cmd.Flags().StringVarP(&c.flagFormat, "format", "f", "table", i18n.G("Format (csv|json|table|yaml|compact)")+"``")
cmd.Flags().BoolVar(&c.flagAllProjects, "all-projects", false, i18n.G("Display network zones from all projects"))

cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
Expand Down Expand Up @@ -124,9 +126,17 @@ func (c *cmdNetworkZoneList) run(cmd *cobra.Command, args []string) error {
return errors.New(i18n.G("Filtering isn't supported yet"))
}

zones, err := resource.server.GetNetworkZones()
if err != nil {
return err
var zones []api.NetworkZone
if c.flagAllProjects {
zones, err = resource.server.GetNetworkZonesAllProjects()
if err != nil {
return err
}
} else {
zones, err = resource.server.GetNetworkZones()
if err != nil {
return err
}
}

data := [][]string{}
Expand All @@ -138,6 +148,10 @@ func (c *cmdNetworkZoneList) run(cmd *cobra.Command, args []string) error {
strUsedBy,
}

if c.flagAllProjects {
details = append([]string{zone.Project}, details...)
}

data = append(data, details)
}

Expand All @@ -149,6 +163,10 @@ func (c *cmdNetworkZoneList) run(cmd *cobra.Command, args []string) error {
i18n.G("USED BY"),
}

if c.flagAllProjects {
header = append([]string{i18n.G("PROJECT")}, header...)
}

return cli.RenderTable(c.flagFormat, header, data, zones)
}

Expand Down
76 changes: 63 additions & 13 deletions lxd/network_zones.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/canonical/lxd/lxd/response"
"github.com/canonical/lxd/lxd/state"
"github.com/canonical/lxd/lxd/util"
"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/entity"
"github.com/canonical/lxd/shared/version"
Expand Down Expand Up @@ -116,6 +118,11 @@ func networkZoneAccessHandler(entitlement auth.Entitlement) func(d *Daemon, r *h
// description: Project name
// type: string
// example: default
// - in: query
// name: all-projects
// description: Retrieve network zones from all projects
// type: boolean
// example: true
// responses:
// "200":
// description: API endpoints
Expand Down Expand Up @@ -165,6 +172,11 @@ func networkZoneAccessHandler(entitlement auth.Entitlement) func(d *Daemon, r *h
// description: Project name
// type: string
// example: default
// - in: query
// name: all-projects
// description: Retrieve network zones from all projects
// type: boolean
// example: true
// responses:
// "200":
// description: API endpoints
Expand Down Expand Up @@ -196,50 +208,88 @@ func networkZoneAccessHandler(entitlement auth.Entitlement) func(d *Daemon, r *h
func networkZonesGet(d *Daemon, r *http.Request) response.Response {
s := d.State()

requestProjectName := request.ProjectParam(r)
effectiveProjectName, _, err := project.NetworkZoneProject(s.DB.Cluster, requestProjectName)
if err != nil {
return response.SmartError(err)
allProjects := shared.IsTrue(request.QueryParam(r, "all-projects"))
requestProjectName := request.QueryParam(r, "project")

// requestProjectName is only valid for project specific requests.
if allProjects && requestProjectName != "" {
return response.BadRequest(errors.New("Cannot specify a project when requesting all projects"))
}

recursion := util.IsRecursionRequest(r)
var effectiveProjectName string
var err error
if !allProjects {
if requestProjectName == "" {
requestProjectName = api.ProjectDefaultName
}

// Project specific requests require an effective project, when "features.networks.zones" is enabled this is the requested project, otherwise it is the default project.
effectiveProjectName, _, err = project.NetworkZoneProject(s.DB.Cluster, requestProjectName)
if err != nil {
return response.SmartError(err)
}

var zoneNames []string
// If the request is project specific, then set effective project name in the request context so that the authorizer can generate the correct URL.
request.SetCtxValue(r, request.CtxEffectiveProjectName, effectiveProjectName)
}

recursion := util.IsRecursionRequest(r)

var zoneNamesMap map[string]string
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
// Get list of Network zones.
zoneNames, err = tx.GetNetworkZonesByProject(ctx, effectiveProjectName)
if allProjects {
zoneNamesMap, err = tx.GetNetworkZones(ctx)
} else {
// Get list of Network zones.
zoneNames, err := tx.GetNetworkZonesByProject(ctx, effectiveProjectName)
if err != nil {
return err
}

// Network zones should be mapped to the requested project for project specific requests.
zoneNamesMap = make(map[string]string, len(zoneNames))
for _, zoneName := range zoneNames {
zoneNamesMap[zoneName] = requestProjectName
}
}

return err
})
if err != nil {
return response.InternalError(err)
}

request.SetCtxValue(r, request.CtxEffectiveProjectName, effectiveProjectName)
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeNetworkZone)
if err != nil {
return response.InternalError(err)
}

resultString := []string{}
resultMap := []api.NetworkZone{}
for _, zoneName := range zoneNames {
if !userHasPermission(entity.NetworkZoneURL(requestProjectName, zoneName)) {
for zoneName, projectName := range zoneNamesMap {
// Check permission for each network zone against the requested project.
if !userHasPermission(entity.NetworkZoneURL(projectName, zoneName)) {
continue
}

if !recursion {
resultString = append(resultString, api.NewURL().Path(version.APIVersion, "network-zones", zoneName).String())
} else {
netzone, err := zone.LoadByNameAndProject(s, effectiveProjectName, zoneName)
var netzone zone.NetworkZone
if !allProjects {
netzone, err = zone.LoadByNameAndProject(s, effectiveProjectName, zoneName)
} else {
netzone, err = zone.LoadByNameAndProject(s, projectName, zoneName)
}

if err != nil {
continue
return response.SmartError(err)
}

netzoneInfo := netzone.Info()
netzoneInfo.UsedBy, _ = netzone.UsedBy() // Ignore errors in UsedBy, will return nil.
netzoneInfo.UsedBy = project.FilterUsedBy(s.Authorizer, r, netzoneInfo.UsedBy)
netzoneInfo.Project = projectName

resultMap = append(resultMap, *netzoneInfo)
}
Expand Down
Loading

0 comments on commit 1527bbf

Please sign in to comment.