diff --git a/lxd/api_internal_recover.go b/lxd/api_internal_recover.go index 173f1b004bda..d02d07b48efe 100644 --- a/lxd/api_internal_recover.go +++ b/lxd/api_internal_recover.go @@ -104,6 +104,16 @@ func internalRecoverScan(s *state.State, userPools []api.StoragePoolsPost, valid return err } + profileConfigs, err := dbCluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := dbCluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + // Convert to map for lookups by project name later. projectProfiles = make(map[string][]*api.Profile) for _, profile := range profiles { @@ -111,7 +121,7 @@ func internalRecoverScan(s *state.State, userPools []api.StoragePoolsPost, valid projectProfiles[profile.Project] = []*api.Profile{} } - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/backup/backup_config_utils.go b/lxd/backup/backup_config_utils.go index 61f5d47de01c..5a12622b087b 100644 --- a/lxd/backup/backup_config_utils.go +++ b/lxd/backup/backup_config_utils.go @@ -50,8 +50,20 @@ func ConfigToInstanceDBArgs(state *state.State, c *config.Config, projectName st return err } + // Get all the profile configs. + profileConfigs, err := cluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + for _, profile := range profiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/db/cluster/instances.go b/lxd/db/cluster/instances.go index f67c54eb6670..51dc1ac239e8 100644 --- a/lxd/db/cluster/instances.go +++ b/lxd/db/cluster/instances.go @@ -77,16 +77,30 @@ type InstanceFilter struct { } // ToAPI converts the database Instance to API type. -func (i *Instance) ToAPI(ctx context.Context, tx *sql.Tx, globalConfig map[string]any) (*api.Instance, error) { +func (i *Instance) ToAPI(ctx context.Context, tx *sql.Tx, globalConfig map[string]any, instanceDevices map[int][]Device, profileConfigs map[int]map[string]string, profileDevices map[int][]Device) (*api.Instance, error) { profiles, err := GetInstanceProfiles(ctx, tx, i.ID) if err != nil { return nil, err } + if profileConfigs == nil { + profileConfigs, err = GetConfig(ctx, tx, "profile") + if err != nil { + return nil, err + } + } + + if profileDevices == nil { + profileDevices, err = GetDevices(ctx, tx, "profile") + if err != nil { + return nil, err + } + } + apiProfiles := make([]api.Profile, 0, len(profiles)) profileNames := make([]string, 0, len(profiles)) for _, p := range profiles { - apiProfile, err := p.ToAPI(ctx, tx) + apiProfile, err := p.ToAPI(ctx, tx, profileConfigs, profileDevices) if err != nil { return nil, err } @@ -95,9 +109,18 @@ func (i *Instance) ToAPI(ctx context.Context, tx *sql.Tx, globalConfig map[strin profileNames = append(profileNames, p.Name) } - devices, err := GetInstanceDevices(ctx, tx, i.ID) - if err != nil { - return nil, err + var devices map[string]Device + if instanceDevices != nil { + devices = map[string]Device{} + + for _, dev := range instanceDevices[i.ID] { + devices[dev.Name] = dev + } + } else { + devices, err = GetInstanceDevices(ctx, tx, i.ID) + if err != nil { + return nil, err + } } apiDevices := DevicesToAPI(devices) diff --git a/lxd/db/cluster/profiles.go b/lxd/db/cluster/profiles.go index baf8fd5d204c..9f05eabac2f4 100644 --- a/lxd/db/cluster/profiles.go +++ b/lxd/db/cluster/profiles.go @@ -51,22 +51,41 @@ type ProfileFilter struct { } // ToAPI returns a cluster Profile as an API struct. -func (p *Profile) ToAPI(ctx context.Context, tx *sql.Tx) (*api.Profile, error) { - config, err := GetProfileConfig(ctx, tx, p.ID) - if err != nil { - return nil, err +func (p *Profile) ToAPI(ctx context.Context, tx *sql.Tx, profileConfigs map[int]map[string]string, profileDevices map[int][]Device) (*api.Profile, error) { + var err error + + var dbConfig map[string]string + if profileConfigs != nil { + dbConfig = profileConfigs[p.ID] + if dbConfig == nil { + dbConfig = map[string]string{} + } + } else { + dbConfig, err = GetProfileConfig(ctx, tx, p.ID) + if err != nil { + return nil, err + } } - devices, err := GetProfileDevices(ctx, tx, p.ID) - if err != nil { - return nil, err + var dbDevices map[string]Device + if profileDevices != nil { + dbDevices = map[string]Device{} + + for _, dev := range profileDevices[p.ID] { + dbDevices[dev.Name] = dev + } + } else { + dbDevices, err = GetProfileDevices(ctx, tx, p.ID) + if err != nil { + return nil, err + } } profile := &api.Profile{ Name: p.Name, Description: p.Description, - Config: config, - Devices: DevicesToAPI(devices), + Config: dbConfig, + Devices: DevicesToAPI(dbDevices), } return profile, nil diff --git a/lxd/db/instances.go b/lxd/db/instances.go index dffe6b41f97d..e386e741abd9 100644 --- a/lxd/db/instances.go +++ b/lxd/db/instances.go @@ -534,6 +534,18 @@ func (c *ClusterTx) instanceProfilesFill(ctx context.Context, snapshotsMode bool return fmt.Errorf("Failed loading profiles: %w", err) } + // Get all the profile configs. + profileConfigs, err := cluster.GetConfig(context.TODO(), c.Tx(), "profile") + if err != nil { + return fmt.Errorf("Failed loading profile configs: %w", err) + } + + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(context.TODO(), c.Tx(), "profile") + if err != nil { + return fmt.Errorf("Failed loading profile devices: %w", err) + } + // Populate profilesByID map entry for referenced profiles. // This way we only call ToAPI() on the profiles actually referenced by the instances in // the list, which can reduce the number of queries run. @@ -543,7 +555,7 @@ func (c *ClusterTx) instanceProfilesFill(ctx context.Context, snapshotsMode bool continue } - profilesByID[profile.ID], err = profile.ToAPI(context.TODO(), c.tx) + profilesByID[profile.ID], err = profile.ToAPI(context.TODO(), c.tx, profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go index f657cfe8d3bc..e03cb74b946b 100644 --- a/lxd/db/profiles.go +++ b/lxd/db/profiles.go @@ -60,7 +60,7 @@ func (c *ClusterTx) GetProfile(ctx context.Context, project, name string) (int64 profile := profiles[0] id := int64(profile.ID) - result, err := profile.ToAPI(ctx, c.tx) + result, err := profile.ToAPI(ctx, c.tx, nil, nil) if err != nil { return -1, nil, err } @@ -77,8 +77,20 @@ func (c *ClusterTx) GetProfiles(ctx context.Context, projectName string, profile return nil, err } + // Get all the profile configs. + profileConfigs, err := cluster.GetConfig(ctx, c.Tx(), "profile") + if err != nil { + return nil, err + } + + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(ctx, c.Tx(), "profile") + if err != nil { + return nil, err + } + for i, profile := range dbProfiles { - apiProfile, err := profile.ToAPI(ctx, c.tx) + apiProfile, err := profile.ToAPI(ctx, c.tx, profileConfigs, profileDevices) if err != nil { return nil, err } diff --git a/lxd/instance_patch.go b/lxd/instance_patch.go index db645b302423..b61855661a1a 100644 --- a/lxd/instance_patch.go +++ b/lxd/instance_patch.go @@ -191,8 +191,18 @@ func instancePatch(d *Daemon, r *http.Request) response.Response { return err } + profileConfigs, err := cluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + for _, profile := range profiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/instance_post.go b/lxd/instance_post.go index f36e5b46aec7..f349454e79b1 100644 --- a/lxd/instance_post.go +++ b/lxd/instance_post.go @@ -509,9 +509,19 @@ func instancePostMigration(s *state.State, inst instance.Instance, newName strin return err } + profileConfigs, err := dbCluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := dbCluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + apiProfiles = make([]api.Profile, 0, len(profiles)) for _, profile := range profiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/instance_put.go b/lxd/instance_put.go index 47289d5461db..563a3aed28f9 100644 --- a/lxd/instance_put.go +++ b/lxd/instance_put.go @@ -139,8 +139,18 @@ func instancePut(d *Daemon, r *http.Request) response.Response { return err } + profileConfigs, err := cluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + for _, profile := range profiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } diff --git a/lxd/instances_post.go b/lxd/instances_post.go index 58b3fa8abef8..116789f6bca3 100644 --- a/lxd/instances_post.go +++ b/lxd/instances_post.go @@ -1171,6 +1171,16 @@ func instancesPost(d *Daemon, r *http.Request) response.Response { return err } + dbProfileConfigs, err := dbCluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + dbProfileDevices, err := dbCluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + profilesByName := make(map[string]dbCluster.Profile, len(dbProfiles)) for _, dbProfile := range dbProfiles { profilesByName[dbProfile.Name] = dbProfile @@ -1182,7 +1192,7 @@ func instancesPost(d *Daemon, r *http.Request) response.Response { return fmt.Errorf("Requested profile %q doesn't exist", profileName) } - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), dbProfileConfigs, dbProfileDevices) if err != nil { return err } diff --git a/lxd/network/acl/acl_load.go b/lxd/network/acl/acl_load.go index e92cde583c42..96e15f62a84b 100644 --- a/lxd/network/acl/acl_load.go +++ b/lxd/network/acl/acl_load.go @@ -145,11 +145,19 @@ func UsedBy(s *state.State, aclProjectName string, usageFunc func(ctx context.Co return err } + // Get all the profile devices. + profileDevicesByID, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + for _, profile := range profiles { - profileDevices[profile.Name], err = cluster.GetProfileDevices(ctx, tx.Tx(), profile.ID) - if err != nil { - return err + devices := map[string]cluster.Device{} + for _, dev := range profileDevicesByID[profile.ID] { + devices[dev.Name] = dev } + + profileDevices[profile.Name] = devices } return nil diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go index 159a12e7e1bc..8b8e5ae61657 100644 --- a/lxd/network/network_utils.go +++ b/lxd/network/network_utils.go @@ -188,17 +188,19 @@ func UsedBy(s *state.State, networkProjectName string, networkID int64, networkN // Look for profiles. Next cheapest to do. err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Get all profiles profiles, err := cluster.GetProfiles(ctx, tx.Tx()) if err != nil { return err } - for _, profile := range profiles { - profileDevices, err := cluster.GetProfileDevices(ctx, tx.Tx(), profile.ID) - if err != nil { - return err - } + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + for _, profile := range profiles { profileProject, err := cluster.GetProject(ctx, tx.Tx(), profile.Project) if err != nil { return err @@ -209,7 +211,12 @@ func UsedBy(s *state.State, networkProjectName string, networkID int64, networkN return err } - inUse, err := usedByProfileDevices(profileDevices, apiProfileProject, networkProjectName, networkName, networkType) + devices := map[string]cluster.Device{} + for _, dev := range profileDevices[profile.ID] { + devices[dev.Name] = dev + } + + inUse, err := usedByProfileDevices(devices, apiProfileProject, networkProjectName, networkName, networkType) if err != nil { return err } diff --git a/lxd/profiles.go b/lxd/profiles.go index 0f64e6feb96b..0b4e53ab9dad 100644 --- a/lxd/profiles.go +++ b/lxd/profiles.go @@ -232,13 +232,23 @@ func profilesGet(d *Daemon, r *http.Request) response.Response { } if recursion { + profileConfigs, err := dbCluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := dbCluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + apiProfiles = make([]*api.Profile, 0, len(profiles)) for _, profile := range profiles { if !userHasPermission(entity.ProfileURL(requestProjectName, profile.Name)) { continue } - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } @@ -465,7 +475,17 @@ func profileGet(d *Daemon, r *http.Request) response.Response { return fmt.Errorf("Fetch profile: %w", err) } - resp, err = profile.ToAPI(ctx, tx.Tx()) + profileConfigs, err := dbCluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + profileDevices, err := dbCluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return err + } + + resp, err = profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return err } @@ -551,7 +571,7 @@ func profilePut(d *Daemon, r *http.Request) response.Response { return fmt.Errorf("Failed to retrieve profile %q: %w", details.profileName, err) } - profile, err = current.ToAPI(ctx, tx.Tx()) + profile, err = current.ToAPI(ctx, tx.Tx(), nil, nil) if err != nil { return err } @@ -651,7 +671,7 @@ func profilePatch(d *Daemon, r *http.Request) response.Response { return fmt.Errorf("Failed to retrieve profile=%q: %w", details.profileName, err) } - profile, err = current.ToAPI(ctx, tx.Tx()) + profile, err = current.ToAPI(ctx, tx.Tx(), nil, nil) if err != nil { return err } diff --git a/lxd/project/limits/permissions.go b/lxd/project/limits/permissions.go index 0eac04af6ad8..57fed7371d5e 100644 --- a/lxd/project/limits/permissions.go +++ b/lxd/project/limits/permissions.go @@ -1268,9 +1268,19 @@ func fetchProject(globalConfig map[string]any, tx *db.ClusterTx, projectName str return nil, fmt.Errorf("Fetch profiles from database: %w", err) } + dbProfileConfigs, err := cluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return nil, fmt.Errorf("Fetch profile configs from database: %w", err) + } + + dbProfileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return nil, fmt.Errorf("Fetch profile devices from database: %w", err) + } + profiles := make([]api.Profile, 0, len(dbProfiles)) for _, profile := range dbProfiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), dbProfileConfigs, dbProfileDevices) if err != nil { return nil, err } @@ -1288,9 +1298,14 @@ func fetchProject(globalConfig map[string]any, tx *db.ClusterTx, projectName str return nil, fmt.Errorf("Fetch project instances from database: %w", err) } + dbInstanceDevices, err := cluster.GetDevices(ctx, tx.Tx(), "instance") + if err != nil { + return nil, fmt.Errorf("Fetch instance devices from database: %w", err) + } + instances := make([]api.Instance, 0, len(dbInstances)) for _, instance := range dbInstances { - apiInstance, err := instance.ToAPI(ctx, tx.Tx(), globalConfig) + apiInstance, err := instance.ToAPI(ctx, tx.Tx(), globalConfig, dbInstanceDevices, dbProfileConfigs, dbProfileDevices) if err != nil { return nil, fmt.Errorf("Failed to get API data for instance %q in project %q: %w", instance.Name, instance.Project, err) } diff --git a/lxd/storage/storage.go b/lxd/storage/storage.go index d692d75a4391..37db73246580 100644 --- a/lxd/storage/storage.go +++ b/lxd/storage/storage.go @@ -209,13 +209,14 @@ func UsedBy(ctx context.Context, s *state.State, pool Pool, firstOnly bool, memb return fmt.Errorf("Failed loading profiles: %w", err) } - for _, profile := range profiles { - profileDevices, err := cluster.GetProfileDevices(ctx, tx.Tx(), profile.ID) - if err != nil { - return fmt.Errorf("Failed loading profile devices: %w", err) - } + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return fmt.Errorf("Failed loading profile devices: %w", err) + } - for _, device := range profileDevices { + for _, profile := range profiles { + for _, device := range profileDevices[profile.ID] { if device.Type != cluster.TypeDisk { continue } diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go index 2cd1e3823cd2..5b120ef2bb64 100644 --- a/lxd/storage/utils.go +++ b/lxd/storage/utils.go @@ -940,8 +940,20 @@ func VolumeUsedByProfileDevices(s *state.State, poolName string, projectName str return fmt.Errorf("Failed loading profiles: %w", err) } + // Get all the profile configs. + profileConfigs, err := cluster.GetConfig(ctx, tx.Tx(), "profile") + if err != nil { + return fmt.Errorf("Failed loading profile configs: %w", err) + } + + // Get all the profile devices. + profileDevices, err := cluster.GetDevices(ctx, tx.Tx(), "profile") + if err != nil { + return fmt.Errorf("Failed loading profile devices: %w", err) + } + for _, profile := range dbProfiles { - apiProfile, err := profile.ToAPI(ctx, tx.Tx()) + apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices) if err != nil { return fmt.Errorf("Failed getting API Profile %q: %w", profile.Name, err) }