<commit_before>/*Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models

import (
	"github.com/skyrings/skyring-common/monitoring"
	"github.com/skyrings/skyring-common/tools/uuid"
	"time"
)

type Node struct {
	NodeId        uuid.UUID              `json:"nodeid"`
	Roles         []string               `json:"roles"`
	Hostname      string                 `json:"hostname"`
	Tags          []string               `json:"tags"`
	ManagementIP4 string                 `json:"management_ip4"`
	ClusterIP4    string                 `json:"cluster_ip4"`
	PublicIP4     string                 `json:"public_ip4"`
	ClusterId     uuid.UUID              `json:"clusterid"`
	Location      string                 `json:"location"`
	Status        NodeStatus             `json:"status"`
	State         NodeState              `json:"state"`
	AlmStatus     AlarmStatus            `json:"almstatus"`
	AlmWarnCount  int                    `json:"almwarncount"`
	AlmCritCount  int                    `json:"almcritcount"`
	Options       map[string]string      `json:"options"`
	CPUs          []Cpu                  `json:"cpus"`
	NetworkInfo   Network                `json:"network_info"`
	StorageDisks  []Disk                 `json:"storage_disks"`
	Memory        Memory                 `json:"memory"`
	OS            OperatingSystem        `json:"os"`
	Enabled       bool                   `json:"enabled"`
	Fingerprint   string                 `json:"saltfingerprint"`
	Utilizations  map[string]Utilization `json:"utilizations"`
}

type Network struct {
	IPv4   []string `bson:"ipv4",json:"ipv4"`     // TODO: use ipv4 type
	IPv6   []string `bson:"ipv6",json:"ipv6"`     // TODO: use ipv6 type
	Subnet []string `bson:"subnet",json:"subnet"` // TODO: use subnet type
}

type Disk struct {
	DevName        string    `bson:"devname",json:"devname"`
	FSType         string    `bson:"fstype",json:"fstype"`
	FSUUID         uuid.UUID `bson:"fsuuid",json:"fsuuid"`
	Model          string    `bson:"model",json:"model"`
	MountPoint     []string  `bson:"mountpoint",json:"mountpoint"`
	Name           string    `bson:"name",json:"name"`
	Parent         string    `bson:"parent",json:"parent"`
	Size           uint64    `bson:"size",json:"size"`
	Type           string    `bson:"type",json:"type"`
	Used           bool      `bson:"used",json:"used"`
	SSD            bool      `bson:"ssd",json:"ssd"`
	Vendor         string    `bson:"vendor",json:"vendor"`
	StorageProfile string    `bson:"storageprofile",json:"storageprofile"`
	DiskId         uuid.UUID `bson:"diskid",json:"diskid"`
}

type Cpu struct {
	Architecture   string `bson:"architecture",json:"architecture"`
	CpuOpMode      string `bson:"cpuopmode",json:"cpuopmode"`
	CPUs           string `bson:"cpus",json:"cpus"`
	VendorId       string `bson:"vendorid",json:"vendorid"`
	ModelName      string `bson:"modelname",json:"modelname"`
	CPUFamily      string `bson:"cpufamily",json:"cpufamily"`
	CPUMHz         string `bson:"cpumhz",json:"cpumhz"`
	Model          string `bson:"model",json:"model"`
	CoresPerSocket string `bson:"corespersocket",json:"corespersocket"`
}

type OperatingSystem struct {
	Name          string `bson:"name",json:"name"`
	OSVersion     string `bson:"osversion",json:"osversion"`
	KernelVersion string `bson:"kernelversion",json:"kernelversion"`
	SELinuxMode   string `bson:"selinuxmode",json:"selinuxmode"`
}

type Memory struct {
	TotalSize string `bson:"totalsize",json:"totalsize"`
	SwapTotal string `bson:"swaptotal",json:"swaptotal"`
	Active    string `bson:"active",json:"active"`
	Type      string `bson:"type",json:"type"`
}

type User struct {
	Username            string   `json:"username"`
	Email               string   `json:"email"`
	Hash                []byte   `json:"hash"`
	Role                string   `json:"role"`
	Groups              []string `json:"groups"`
	Type                int      `json:"type"`
	Status              bool     `json:"status"`
	FirstName           string   `json:"firstname"`
	LastName            string   `json:"lastname"`
	NotificationEnabled bool     `json:"notificationenabled"`
}

type Cluster struct {
	ClusterId           uuid.UUID              `json:"clusterid"`
	Name                string                 `json:"name"`
	CompatVersion       string                 `json:"compat_version"`
	Type                string                 `json:"type"`
	WorkLoad            string                 `json:"workload"`
	Status              ClusterStatus          `json:"status"`
	Tags                []string               `json:"tags"`
	Options             map[string]interface{} `json:"options"`
	OpenStackServices   []string               `json:"openstack_services"`
	Networks            ClusterNetworks        `json:"networks"`
	Monitoring          MonitoringState        `json:"monitoring"`
	MonitoringInterval  int                    `json:"monitoringinterval"`
	State               ClusterState           `json:"state"`
	AlmStatus           AlarmStatus            `json:"almstatus"`
	AlmWarnCount        int                    `json:"almwarncount"`
	AlmCritCount        int                    `json:"almcritcount"`
	Usage               Utilization            `json:"usage"`
	StorageProfileUsage map[string]Utilization `json:"storageprofileusage"`
	ObjectCount         map[string]int64       `json:"objectcount"`
	AutoExpand          bool                   `json:"autoexpand"`
	JournalSize         string                 `json:"journalsize"`
	Utilizations        map[string]interface{} `json:"utilizations"`
}

type System struct {
	Name                      string                            `json:"name"`
	Usage                     Utilization                       `json:"usage"`
	StorageProfileUsage       map[string]map[string]interface{} `json:"storageprofileusage"`
	StorageCount              map[string]int                    `json:"storagecount"`
	SLUCount                  map[string]int                    `json:"slucount"`
	NodesCount                map[string]int                    `json:"nodescount"`
	ClustersCount             map[string]int                    `json:"clusterscount"`
	ProviderMonitoringDetails map[string]map[string]interface{} `json:"providermonitoringdetails"`
	MostUsedStorages          []StorageUsage                    `json:"storageusage"`
	Utilizations              map[string]interface{}            `json:"utilizations"`
}

type Utilization struct {
	Used        int64   `json:"used"`
	Total       int64   `json:"total"`
	PercentUsed float64 `json:"percentused"`
}

type MonitoringState struct {
	Plugins    []monitoring.Plugin `json:"plugins"`
	StaleNodes []string            `json:"stalenodes"`
}

type ClusterNetworks struct {
	Cluster string `json:"cluster"`
	Public  string `json:"public"`
}

type StorageLogicalUnit struct {
	SluId             uuid.UUID              `json:"sluid"`
	Name              string                 `json:"name"`
	Type              int                    `json:"type"`
	ClusterId         uuid.UUID              `json:"clusterid"`
	NodeId            uuid.UUID              `json:"nodeid"`
	StorageIds        []uuid.UUID            `json:"storageid"`
	StorageDeviceId   uuid.UUID              `json:"storagedeviceid"`
	StorageDeviceSize uint64                 `json:"storagedevicesize"`
	Status            SluStatus              `json:"status"`
	Options           map[string]interface{} `json:"options"`
	StorageProfile    string                 `json:"storageprofile"`
	State             string                 `json:"state"`
	AlmStatus         AlarmStatus            `json:"almstatus"`
	AlmWarnCount      int                    `json:"almwarncount"`
	AlmCritCount      int                    `json:"almcritcount"`
	Usage             Utilization            `json:"usage"`
}

type Storage struct {
	StorageId           uuid.UUID         `json:"storageid"`
	Name                string            `json:"name"`
	Type                string            `json:"type"`
	Tags                []string          `json:"tags"`
	ClusterId           uuid.UUID         `json:"clusterid"`
	Size                string            `json:"size"`
	Status              StorageStatus     `json:"status"`
	Replicas            int               `json:"replicas"`
	Profile             string            `json:"profile"`
	SnapshotsEnabled    bool              `json:"snapshots_enabled"`
	SnapshotScheduleIds []uuid.UUID       `json:"snapshot_schedule_ids"`
	QuotaEnabled        bool              `json:"quota_enabled"`
	QuotaParams         map[string]string `json:"quota_params"`
	Options             map[string]string `json:"options"`
	Usage               Utilization       `json:"usage"`
	State               string            `json:"state"`
	AlmStatus           AlarmStatus       `json:"almstatus"`
	AlmWarnCount        int               `json:"almwarncount"`
	AlmCritCount        int               `json:"almcritcount"`
	SluIds              []uuid.UUID       `json:"slus"`
}

type BlockDevice struct {
	Id                  uuid.UUID         `json:"id"`
	Name                string            `json:"name"`
	Tags                []string          `json:"tags"`
	ClusterId           uuid.UUID         `json:"clusterid"`
	ClusterName         string            `json:"clustername"`
	StorageId           uuid.UUID         `json:"storageid"`
	StorageName         string            `json:"storagename"`
	Size                string            `json:"size"`
	SnapshotsEnabled    bool              `json:"snapshots_enabled"`
	SnapshotScheduleIds []uuid.UUID       `json:"snapshot_schedule_ids"`
	QuotaEnabled        bool              `json:"quota_enabled"`
	QuotaParams         map[string]string `json:"quota_params"`
	Options             map[string]string `json:"options"`
	Usage               Utilization       `json:"usage"`
	AlmStatus           AlarmStatus       `json:"almstatus"`
	AlmWarnCount        int               `json:"almwarncount"`
	AlmCritCount        int               `json:"almcritcount"`
}

type SnapshotSchedule struct {
	Id            uuid.UUID `json:"id"`
	Recurrence    string    `json:"recurrence"`
	Interval      int       `json:"interval"`
	ExecutionTime string    `json:"execution_time"`
	Days          []string  `json:"days"`
	StartFrom     string    `json:"start_from"`
	EndBy         string    `json:"endby"`
}

type Status struct {
	Timestamp time.Time
	Message   string
}

type AppTask struct {
	Id          uuid.UUID         `json:"id"`
	Name        string            `json:"name"`
	Owner       string            `json:"owner"`
	ParentId    uuid.UUID         `json:"parentid"`
	Started     bool              `json:"started"`
	Completed   bool              `json:"completed"`
	StatusList  []Status          `json:"statuslist"`
	Tag         map[string]string `json:"tag"`
	LastUpdated time.Time         `json:"lastupdated"`
	SubTasks    []uuid.UUID       `json:"subtasks"`
	Status      TaskStatus        `json:"status"`
}

type DiskProfile struct {
	Type  DiskType `json:"disktype"`
	Speed int      `json:"speed"`
}

type StorageProfile struct {
	Name     string      `json:"name"`
	Rule     DiskProfile `json:"rule"`
	Priority int         `json:"priority"`
	Default  bool        `json:"default"`
}

type ExternalUsers struct {
	Users      []User
	TotalCount int
	StartIndex int
	EndIndex   int
}

type Directory struct {
	LdapServer  string
	Port        uint
	Base        string
	DomainAdmin string
	Password    string
	Uid         string
	FirstName   string
	LastName    string
	DisplayName string
	Email       string
}

type ClusterNotificationSubscription struct {
	ClusterId     uuid.UUID                  `json:"clusterid"`
	Notifications []NotificationSubscription `json:"notifications"`
}
<commit_msg>ldap: add ldap directory service type attribute<commit_after>/*Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package models

import (
	"github.com/skyrings/skyring-common/monitoring"
	"github.com/skyrings/skyring-common/tools/uuid"
	"time"
)

type Node struct {
	NodeId        uuid.UUID              `json:"nodeid"`
	Roles         []string               `json:"roles"`
	Hostname      string                 `json:"hostname"`
	Tags          []string               `json:"tags"`
	ManagementIP4 string                 `json:"management_ip4"`
	ClusterIP4    string                 `json:"cluster_ip4"`
	PublicIP4     string                 `json:"public_ip4"`
	ClusterId     uuid.UUID              `json:"clusterid"`
	Location      string                 `json:"location"`
	Status        NodeStatus             `json:"status"`
	State         NodeState              `json:"state"`
	AlmStatus     AlarmStatus            `json:"almstatus"`
	AlmWarnCount  int                    `json:"almwarncount"`
	AlmCritCount  int                    `json:"almcritcount"`
	Options       map[string]string      `json:"options"`
	CPUs          []Cpu                  `json:"cpus"`
	NetworkInfo   Network                `json:"network_info"`
	StorageDisks  []Disk                 `json:"storage_disks"`
	Memory        Memory                 `json:"memory"`
	OS            OperatingSystem        `json:"os"`
	Enabled       bool                   `json:"enabled"`
	Fingerprint   string                 `json:"saltfingerprint"`
	Utilizations  map[string]Utilization `json:"utilizations"`
}

type Network struct {
	IPv4   []string `bson:"ipv4",json:"ipv4"`     // TODO: use ipv4 type
	IPv6   []string `bson:"ipv6",json:"ipv6"`     // TODO: use ipv6 type
	Subnet []string `bson:"subnet",json:"subnet"` // TODO: use subnet type
}

type Disk struct {
	DevName        string    `bson:"devname",json:"devname"`
	FSType         string    `bson:"fstype",json:"fstype"`
	FSUUID         uuid.UUID `bson:"fsuuid",json:"fsuuid"`
	Model          string    `bson:"model",json:"model"`
	MountPoint     []string  `bson:"mountpoint",json:"mountpoint"`
	Name           string    `bson:"name",json:"name"`
	Parent         string    `bson:"parent",json:"parent"`
	Size           uint64    `bson:"size",json:"size"`
	Type           string    `bson:"type",json:"type"`
	Used           bool      `bson:"used",json:"used"`
	SSD            bool      `bson:"ssd",json:"ssd"`
	Vendor         string    `bson:"vendor",json:"vendor"`
	StorageProfile string    `bson:"storageprofile",json:"storageprofile"`
	DiskId         uuid.UUID `bson:"diskid",json:"diskid"`
}

type Cpu struct {
	Architecture   string `bson:"architecture",json:"architecture"`
	CpuOpMode      string `bson:"cpuopmode",json:"cpuopmode"`
	CPUs           string `bson:"cpus",json:"cpus"`
	VendorId       string `bson:"vendorid",json:"vendorid"`
	ModelName      string `bson:"modelname",json:"modelname"`
	CPUFamily      string `bson:"cpufamily",json:"cpufamily"`
	CPUMHz         string `bson:"cpumhz",json:"cpumhz"`
	Model          string `bson:"model",json:"model"`
	CoresPerSocket string `bson:"corespersocket",json:"corespersocket"`
}

type OperatingSystem struct {
	Name          string `bson:"name",json:"name"`
	OSVersion     string `bson:"osversion",json:"osversion"`
	KernelVersion string `bson:"kernelversion",json:"kernelversion"`
	SELinuxMode   string `bson:"selinuxmode",json:"selinuxmode"`
}

type Memory struct {
	TotalSize string `bson:"totalsize",json:"totalsize"`
	SwapTotal string `bson:"swaptotal",json:"swaptotal"`
	Active    string `bson:"active",json:"active"`
	Type      string `bson:"type",json:"type"`
}

type User struct {
	Username            string   `json:"username"`
	Email               string   `json:"email"`
	Hash                []byte   `json:"hash"`
	Role                string   `json:"role"`
	Groups              []string `json:"groups"`
	Type                int      `json:"type"`
	Status              bool     `json:"status"`
	FirstName           string   `json:"firstname"`
	LastName            string   `json:"lastname"`
	NotificationEnabled bool     `json:"notificationenabled"`
}

type Cluster struct {
	ClusterId           uuid.UUID              `json:"clusterid"`
	Name                string                 `json:"name"`
	CompatVersion       string                 `json:"compat_version"`
	Type                string                 `json:"type"`
	WorkLoad            string                 `json:"workload"`
	Status              ClusterStatus          `json:"status"`
	Tags                []string               `json:"tags"`
	Options             map[string]interface{} `json:"options"`
	OpenStackServices   []string               `json:"openstack_services"`
	Networks            ClusterNetworks        `json:"networks"`
	Monitoring          MonitoringState        `json:"monitoring"`
	MonitoringInterval  int                    `json:"monitoringinterval"`
	State               ClusterState           `json:"state"`
	AlmStatus           AlarmStatus            `json:"almstatus"`
	AlmWarnCount        int                    `json:"almwarncount"`
	AlmCritCount        int                    `json:"almcritcount"`
	Usage               Utilization            `json:"usage"`
	StorageProfileUsage map[string]Utilization `json:"storageprofileusage"`
	ObjectCount         map[string]int64       `json:"objectcount"`
	AutoExpand          bool                   `json:"autoexpand"`
	JournalSize         string                 `json:"journalsize"`
	Utilizations        map[string]interface{} `json:"utilizations"`
}

type System struct {
	Name                      string                            `json:"name"`
	Usage                     Utilization                       `json:"usage"`
	StorageProfileUsage       map[string]map[string]interface{} `json:"storageprofileusage"`
	StorageCount              map[string]int                    `json:"storagecount"`
	SLUCount                  map[string]int                    `json:"slucount"`
	NodesCount                map[string]int                    `json:"nodescount"`
	ClustersCount             map[string]int                    `json:"clusterscount"`
	ProviderMonitoringDetails map[string]map[string]interface{} `json:"providermonitoringdetails"`
	MostUsedStorages          []StorageUsage                    `json:"storageusage"`
	Utilizations              map[string]interface{}            `json:"utilizations"`
}

type Utilization struct {
	Used        int64   `json:"used"`
	Total       int64   `json:"total"`
	PercentUsed float64 `json:"percentused"`
}

type MonitoringState struct {
	Plugins    []monitoring.Plugin `json:"plugins"`
	StaleNodes []string            `json:"stalenodes"`
}

type ClusterNetworks struct {
	Cluster string `json:"cluster"`
	Public  string `json:"public"`
}

type StorageLogicalUnit struct {
	SluId             uuid.UUID              `json:"sluid"`
	Name              string                 `json:"name"`
	Type              int                    `json:"type"`
	ClusterId         uuid.UUID              `json:"clusterid"`
	NodeId            uuid.UUID              `json:"nodeid"`
	StorageIds        []uuid.UUID            `json:"storageid"`
	StorageDeviceId   uuid.UUID              `json:"storagedeviceid"`
	StorageDeviceSize uint64                 `json:"storagedevicesize"`
	Status            SluStatus              `json:"status"`
	Options           map[string]interface{} `json:"options"`
	StorageProfile    string                 `json:"storageprofile"`
	State             string                 `json:"state"`
	AlmStatus         AlarmStatus            `json:"almstatus"`
	AlmWarnCount      int                    `json:"almwarncount"`
	AlmCritCount      int                    `json:"almcritcount"`
	Usage             Utilization            `json:"usage"`
}

type Storage struct {
	StorageId           uuid.UUID         `json:"storageid"`
	Name                string            `json:"name"`
	Type                string            `json:"type"`
	Tags                []string          `json:"tags"`
	ClusterId           uuid.UUID         `json:"clusterid"`
	Size                string            `json:"size"`
	Status              StorageStatus     `json:"status"`
	Replicas            int               `json:"replicas"`
	Profile             string            `json:"profile"`
	SnapshotsEnabled    bool              `json:"snapshots_enabled"`
	SnapshotScheduleIds []uuid.UUID       `json:"snapshot_schedule_ids"`
	QuotaEnabled        bool              `json:"quota_enabled"`
	QuotaParams         map[string]string `json:"quota_params"`
	Options             map[string]string `json:"options"`
	Usage               Utilization       `json:"usage"`
	State               string            `json:"state"`
	AlmStatus           AlarmStatus       `json:"almstatus"`
	AlmWarnCount        int               `json:"almwarncount"`
	AlmCritCount        int               `json:"almcritcount"`
	SluIds              []uuid.UUID       `json:"slus"`
}

type BlockDevice struct {
	Id                  uuid.UUID         `json:"id"`
	Name                string            `json:"name"`
	Tags                []string          `json:"tags"`
	ClusterId           uuid.UUID         `json:"clusterid"`
	ClusterName         string            `json:"clustername"`
	StorageId           uuid.UUID         `json:"storageid"`
	StorageName         string            `json:"storagename"`
	Size                string            `json:"size"`
	SnapshotsEnabled    bool              `json:"snapshots_enabled"`
	SnapshotScheduleIds []uuid.UUID       `json:"snapshot_schedule_ids"`
	QuotaEnabled        bool              `json:"quota_enabled"`
	QuotaParams         map[string]string `json:"quota_params"`
	Options             map[string]string `json:"options"`
	Usage               Utilization       `json:"usage"`
	AlmStatus           AlarmStatus       `json:"almstatus"`
	AlmWarnCount        int               `json:"almwarncount"`
	AlmCritCount        int               `json:"almcritcount"`
}

type SnapshotSchedule struct {
	Id            uuid.UUID `json:"id"`
	Recurrence    string    `json:"recurrence"`
	Interval      int       `json:"interval"`
	ExecutionTime string    `json:"execution_time"`
	Days          []string  `json:"days"`
	StartFrom     string    `json:"start_from"`
	EndBy         string    `json:"endby"`
}

type Status struct {
	Timestamp time.Time
	Message   string
}

type AppTask struct {
	Id          uuid.UUID         `json:"id"`
	Name        string            `json:"name"`
	Owner       string            `json:"owner"`
	ParentId    uuid.UUID         `json:"parentid"`
	Started     bool              `json:"started"`
	Completed   bool              `json:"completed"`
	StatusList  []Status          `json:"statuslist"`
	Tag         map[string]string `json:"tag"`
	LastUpdated time.Time         `json:"lastupdated"`
	SubTasks    []uuid.UUID       `json:"subtasks"`
	Status      TaskStatus        `json:"status"`
}

type DiskProfile struct {
	Type  DiskType `json:"disktype"`
	Speed int      `json:"speed"`
}

type StorageProfile struct {
	Name     string      `json:"name"`
	Rule     DiskProfile `json:"rule"`
	Priority int         `json:"priority"`
	Default  bool        `json:"default"`
}

type ExternalUsers struct {
	Users      []User
	TotalCount int
	StartIndex int
	EndIndex   int
}

type Directory struct {
	LdapServer  string
	Type        string
	Port        uint
	Base        string
	DomainAdmin string
	Password    string
	Uid         string
	FirstName   string
	LastName    string
	DisplayName string
	Email       string
}

type ClusterNotificationSubscription struct {
	ClusterId     uuid.UUID                  `json:"clusterid"`
	Notifications []NotificationSubscription `json:"notifications"`
}
<|endoftext|>


<commit_before>package google

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/hashicorp/errwrap"
)

const defaultBatchSendIntervalSec = 3

// RequestBatcher is a global batcher object that keeps track of
// existing batches.
// In general, a batcher should be created per service that requires batching
// in order to prevent blocking batching for one service due to another,
// and to minimize the possibility of overlap in batchKey formats
// (see SendRequestWithTimeout)
type RequestBatcher struct {
	sync.Mutex

	*batchingConfig
	parentCtx context.Context
	batches   map[string]*startedBatch
	debugId   string
}

// BatchRequest represents a single request to a global batcher.
type BatchRequest struct {
	// ResourceName represents the underlying resource for which
	// a request is made. Its format is determined by what SendF expects, but
	// typically should be the name of the parent GCP resource being changed.
	ResourceName string

	// Body is this request's data to be passed to SendF, and may be combined
	// with other bodies using CombineF.
	Body interface{}

	// CombineF function determines how to combine bodies from two batches.
	CombineF batcherCombineFunc

	// SendF function determines how to actually send a batched request to a
	// third party service. The arguments given to this function are
	// (ResourceName, Body) where Body may have been combined with other request
	// Bodies.
	SendF batcherSendFunc

	// ID for debugging request. This should be specific to a single request
	// (i.e. per Terraform resource)
	DebugId string
}

// These types are meant to be the public interface to batchers. They define
// logic to manage batch data type and behavior, and require service-specific
// implementations per type of request per service.
// Function type for combine existing batches and additional batch data
type batcherCombineFunc func(body interface{}, toAdd interface{}) (interface{}, error)

// Function type for sending a batch request
type batcherSendFunc func(resourceName string, body interface{}) (interface{}, error)

// batchResponse bundles an API response (data, error) tuple.
type batchResponse struct {
	body interface{}
	err  error
}

// startedBatch refers to a processed batch whose timer to send the request has
// already been started. The responses for the request is sent to each listener
// channel, representing parallel callers that are waiting on requests
// combined into this batch.
type startedBatch struct {
	batchKey string
	*BatchRequest

	listeners []chan batchResponse
	timer     *time.Timer
}

// batchingConfig contains user configuration for controlling batch requests.
type batchingConfig struct {
	sendAfter      time.Duration
	enableBatching bool
}

// Initializes a new batcher.
func NewRequestBatcher(debugId string, ctx context.Context, config *batchingConfig) *RequestBatcher {
	batcher := &RequestBatcher{
		debugId:        debugId,
		parentCtx:      ctx,
		batchingConfig: config,
		batches:        make(map[string]*startedBatch),
	}

	go func(b *RequestBatcher) {
		<-ctx.Done()
		b.stop()
	}(batcher)

	return batcher
}

func (b *RequestBatcher) stop() {
	b.Lock()
	defer b.Unlock()

	log.Printf("[DEBUG] Stopping batcher %q", b.debugId)
	for batchKey, batch := range b.batches {
		log.Printf("[DEBUG] Cleaning up batch request %q", batchKey)
		batch.timer.Stop()
		for _, l := range batch.listeners {
			close(l)
		}
	}
}

// SendRequestWithTimeout is expected to be called per parallel call.
// It manages waiting on the result of a batch request.
//
// Batch requests are grouped by the given batchKey. batchKey
// should be unique to the API request being sent, most likely similar to
// the HTTP request URL with GCP resource ID included in the URL (the caller
// may choose to use a key with method if needed to diff GET/read and
// POST/create)
//
// As an example, for google_project_service, the
// batcher is called to batch services.batchEnable() calls for a project
// $PROJECT. The calling code uses the template
// "serviceusage:projects/$PROJECT/services:batchEnable", which mirrors the HTTP request:
// POST https://serviceusage.googleapis.com/v1/projects/$PROJECT/services:batchEnable
func (b *RequestBatcher) SendRequestWithTimeout(batchKey string, request *BatchRequest, timeout time.Duration) (interface{}, error) {
	if request == nil {
		return nil, fmt.Errorf("error, cannot request batching for nil BatchRequest")
	}
	if request.CombineF == nil {
		return nil, fmt.Errorf("error, cannot request batching for BatchRequest with nil CombineF")
	}
	if request.SendF == nil {
		return nil, fmt.Errorf("error, cannot request batching for BatchRequest with nil SendF")
	}
	if !b.enableBatching {
		log.Printf("[DEBUG] Batching is disabled, sending single request for %q", request.DebugId)
		return request.SendF(request.ResourceName, request.Body)
	}

	respCh, err := b.registerBatchRequest(batchKey, request)
	if err != nil {
		return nil, fmt.Errorf("error adding request to batch: %s", err)
	}

	ctx, cancel := context.WithTimeout(b.parentCtx, timeout)
	defer cancel()

	select {
	case resp := <-respCh:
		if resp.err != nil {
			// use wrapf so we can potentially extract the original error type
			errMsg := fmt.Sprintf(
				"Batch %q for request %q returned error: {{err}}. To debug individual requests, try disabling batching: https://www.terraform.io/docs/providers/google/guides/provider_reference.html#enable_batching",
				batchKey, request.DebugId)
			return nil, errwrap.Wrapf(errMsg, resp.err)
		}
		return resp.body, nil
	case <-ctx.Done():
		break
	}
	return nil, fmt.Errorf("Request %s timed out after %v", batchKey, timeout)
}

// registerBatchRequest safely sees if an existing batch has been started
// with the given batchKey. If a batch exists, this will combine the new
// request into this existing batch. Else, this method manages starting a new
// batch and adding it to the RequestBatcher's started batches.
func (b *RequestBatcher) registerBatchRequest(batchKey string, newRequest *BatchRequest) (<-chan batchResponse, error) {
	b.Lock()
	defer b.Unlock()

	// If batch already exists, combine this request into existing request.
	if batch, ok := b.batches[batchKey]; ok {
		return batch.addRequest(newRequest)
	}

	log.Printf("[DEBUG] Creating new batch %q from request %q", newRequest.DebugId, batchKey)
	// The calling goroutine will need a channel to wait on for a response.
	respCh := make(chan batchResponse, 1)

	// Create a new batch.
	b.batches[batchKey] = &startedBatch{
		BatchRequest: newRequest,
		batchKey:     batchKey,
		listeners:    []chan batchResponse{respCh},
	}

	// Start a timer to send the request
	b.batches[batchKey].timer = time.AfterFunc(b.sendAfter, func() {
		batch := b.popBatch(batchKey)

		var resp batchResponse
		if batch == nil {
			log.Printf("[DEBUG] Batch not found in saved batches, running single request batch %q", batchKey)
			resp = newRequest.send()
		} else {
			log.Printf("[DEBUG] Sending batch %q combining %d requests)", batchKey, len(batch.listeners))
			resp = batch.send()
		}

		// Send message to all goroutines waiting on result.
		for _, ch := range batch.listeners {
			ch <- resp
			close(ch)
		}
	})

	return respCh, nil
}

// popBatch safely gets and removes a batch with given batchkey from the
// RequestBatcher's started batches.
func (b *RequestBatcher) popBatch(batchKey string) *startedBatch {
	b.Lock()
	defer b.Unlock()

	batch, ok := b.batches[batchKey]
	if !ok {
		log.Printf("[DEBUG] Batch with ID %q not found in batcher", batchKey)
		return nil
	}

	delete(b.batches, batchKey)
	return batch
}

func (batch *startedBatch) addRequest(newRequest *BatchRequest) (<-chan batchResponse, error) {
	log.Printf("[DEBUG] Adding batch request %q to existing batch %q", newRequest.DebugId, batch.batchKey)
	if batch.CombineF == nil {
		return nil, fmt.Errorf("Provider Error: unable to add request %q to batch %q with no CombineF", newRequest.DebugId, batch.batchKey)
	}
	newBody, err := batch.CombineF(batch.Body, newRequest.Body)
	if err != nil {
		return nil, fmt.Errorf("Provider Error: Unable to combine request %q data into existing batch %q: %v", newRequest.DebugId, batch.batchKey, err)
	}
	batch.Body = newBody

	log.Printf("[DEBUG] Added batch request %q to batch. New batch body: %v", newRequest.DebugId, batch.Body)

	respCh := make(chan batchResponse, 1)
	batch.listeners = append(batch.listeners, respCh)
	return respCh, nil
}

func (req *BatchRequest) send() batchResponse {
	if req.SendF == nil {
		return batchResponse{
			err: fmt.Errorf("provider error: Batch request has no SendBatch function"),
		}
	}
	v, err := req.SendF(req.ResourceName, req.Body)
	return batchResponse{v, err}
}
<commit_msg>Allow for retries of single requests in a batch on failure (#313)<commit_after>package google

import (
	"context"
	"fmt"
	"github.com/hashicorp/errwrap"
	"log"
	"sync"
	"time"
)

const defaultBatchSendIntervalSec = 3

// RequestBatcher keeps track of batched requests globally.
// It should be created at a provider level. In general, one
// should be created per service that requires batching to:
//   - prevent blocking batching for one service due to another,
//   - minimize the possibility of overlap in batchKey formats (see SendRequestWithTimeout)
type RequestBatcher struct {
	sync.Mutex

	*batchingConfig
	parentCtx context.Context
	batches   map[string]*startedBatch
	debugId   string
}

// These types are meant to be the public interface to batchers. They define
// batch data format and logic to send/combine batches, i.e. they require
// specific implementations per type of request.
type (
	// BatchRequest represents a single request to a global batcher.
	BatchRequest struct {
		// ResourceName represents the underlying resource for which
		// a request is made. Its format is determined by what SendF expects, but
		// typically should be the name of the parent GCP resource being changed.
		ResourceName string

		// Body is this request's data to be passed to SendF, and may be combined
		// with other bodies using CombineF.
		Body interface{}

		// CombineF function determines how to combine bodies from two batches.
		CombineF BatcherCombineFunc

		// SendF function determines how to actually send a batched request to a
		// third party service. The arguments given to this function are
		// (ResourceName, Body) where Body may have been combined with other request
		// Bodies.
		SendF BatcherSendFunc

		// ID for debugging request. This should be specific to a single request
		// (i.e. per Terraform resource)
		DebugId string
	}

	// BatcherCombineFunc is a function type for combine existing batches and additional batch data
	BatcherCombineFunc func(body interface{}, toAdd interface{}) (interface{}, error)

	// BatcherSendFunc is a function type for sending a batch request
	BatcherSendFunc func(resourceName string, body interface{}) (interface{}, error)
)

// batchResponse bundles an API response (data, error) tuple.
type batchResponse struct {
	body interface{}
	err  error
}

func (br *batchResponse) IsError() bool {
	return br.err != nil
}

// startedBatch refers to a registered batch to group batch requests coming in.
// The timer manages the time after which a given batch is sent.
type startedBatch struct {
	batchKey string

	// Combined Batch Request
	*BatchRequest

	// subscribers is a registry of the requests (batchSubscriber) combined into this batcher.

	subscribers []batchSubscriber

	timer *time.Timer
}

// batchSubscriber contains information required for a single request for a startedBatch.
type batchSubscriber struct {
	// singleRequest is the original request this subscriber represents
	singleRequest *BatchRequest

	// respCh is the channel created to communicate the result to a waiting goroutine.s
	respCh chan batchResponse
}

// batchingConfig contains user configuration for controlling batch requests.
type batchingConfig struct {
	sendAfter      time.Duration
	enableBatching bool
}

// Initializes a new batcher.
func NewRequestBatcher(debugId string, ctx context.Context, config *batchingConfig) *RequestBatcher {
	batcher := &RequestBatcher{
		debugId:        debugId,
		parentCtx:      ctx,
		batchingConfig: config,
		batches:        make(map[string]*startedBatch),
	}

	// Start goroutine to managing stopping the batcher if the provider-level parent context is closed.
	go func(b *RequestBatcher) {
		// Block until parent context is closed
		<-b.parentCtx.Done()

		log.Printf("[DEBUG] parent context canceled, cleaning up batcher batches")
		b.stop()
	}(batcher)

	return batcher
}

func (b *RequestBatcher) stop() {
	b.Lock()
	defer b.Unlock()

	log.Printf("[DEBUG] Stopping batcher %q", b.debugId)
	for batchKey, batch := range b.batches {
		log.Printf("[DEBUG] Cancelling started batch for batchKey %q", batchKey)
		batch.timer.Stop()
		for _, l := range batch.subscribers {
			close(l.respCh)
		}
	}
}

// SendRequestWithTimeout is a blocking call for making a single request, run alone or as part of a batch.
// It manages registering the single request with the batcher and waiting on the result.
//
// Params:
// batchKey: A string to group batchable requests. It should be unique to the API request being sent, similar to
// the HTTP request URL with GCP resource ID included in the URL (the caller
// may choose to use a key with method if needed to diff GET/read and
// POST/create)
//
// As an example, for google_project_service, the
// batcher is called to batch services.batchEnable() calls for a project
// $PROJECT. The calling code uses the template
// "serviceusage:projects/$PROJECT/services:batchEnable", which mirrors the HTTP request:
// POST https://serviceusage.googleapis.com/v1/projects/$PROJECT/services:batchEnable
func (b *RequestBatcher) SendRequestWithTimeout(batchKey string, request *BatchRequest, timeout time.Duration) (interface{}, error) {
	if request == nil {
		return nil, fmt.Errorf("error, cannot request batching for nil BatchRequest")
	}
	if request.CombineF == nil {
		return nil, fmt.Errorf("error, cannot request batching for BatchRequest with nil CombineF")
	}
	if request.SendF == nil {
		return nil, fmt.Errorf("error, cannot request batching for BatchRequest with nil SendF")
	}
	if !b.enableBatching {
		log.Printf("[DEBUG] Batching is disabled, sending single request for %q", request.DebugId)
		return request.SendF(request.ResourceName, request.Body)
	}

	respCh, err := b.registerBatchRequest(batchKey, request)
	if err != nil {
		return nil, fmt.Errorf("error adding request to batch: %s", err)
	}

	ctx, cancel := context.WithTimeout(b.parentCtx, timeout)
	defer cancel()

	select {
	case resp := <-respCh:
		if resp.err != nil {
			// use wrapf so we can potentially extract the original error type
			errMsg := fmt.Sprintf(
				"Batch %q for request %q returned error: {{err}}. To debug individual requests, try disabling batching: https://www.terraform.io/docs/providers/google/guides/provider_reference.html#enable_batching",
				batchKey, request.DebugId)
			return nil, errwrap.Wrapf(errMsg, resp.err)
		}
		return resp.body, nil
	case <-ctx.Done():
		break
	}
	return nil, fmt.Errorf("Request %s timed out after %v", batchKey, timeout)
}

// registerBatchRequest safely sees if an existing batch has been started
// with the given batchKey. If a batch exists, this will combine the new
// request into this existing batch. Else, this method manages starting a new
// batch and adding it to the RequestBatcher's started batches.
func (b *RequestBatcher) registerBatchRequest(batchKey string, newRequest *BatchRequest) (<-chan batchResponse, error) {
	b.Lock()
	defer b.Unlock()

	// If batch already exists, combine this request into existing request.
	if batch, ok := b.batches[batchKey]; ok {
		return batch.addRequest(newRequest)
	}

	// Batch doesn't exist for given batch key - create a new batch.

	log.Printf("[DEBUG] Creating new batch %q from request %q", newRequest.DebugId, batchKey)

	// The calling goroutine will need a channel to wait on for a response.
	respCh := make(chan batchResponse, 1)
	sub := batchSubscriber{
		singleRequest: newRequest,
		respCh:        respCh,
	}

	// Create a new batch with copy of the given batch request.
	b.batches[batchKey] = &startedBatch{
		BatchRequest: &BatchRequest{
			ResourceName: newRequest.ResourceName,
			Body:         newRequest.Body,
			CombineF:     newRequest.CombineF,
			SendF:        newRequest.SendF,
			DebugId:      fmt.Sprintf("Combined batch for started batch %q", batchKey),
		},
		batchKey:    batchKey,
		subscribers: []batchSubscriber{sub},
	}

	// Start a timer to send the request
	b.batches[batchKey].timer = time.AfterFunc(b.sendAfter, func() {
		batch := b.popBatch(batchKey)
		if batch == nil {
			log.Printf("[ERROR] batch should have been added to saved batches - just run as single request %q", newRequest.DebugId)
			respCh <- newRequest.send()
			close(respCh)
		} else {
			b.sendBatchWithSingleRetry(batchKey, batch)
		}
	})

	return respCh, nil
}

func (b *RequestBatcher) sendBatchWithSingleRetry(batchKey string, batch *startedBatch) {
	log.Printf("[DEBUG] Sending batch %q combining %d requests)", batchKey, len(batch.subscribers))
	resp := batch.send()

	// If the batch failed and combines more than one request, retry each single request.
	if resp.IsError() && len(batch.subscribers) > 1 {
		log.Printf("[DEBUG] Batch failed with error: %v", resp.err)
		log.Printf("[DEBUG] Sending each request in batch separately")
		for _, sub := range batch.subscribers {
			log.Printf("[DEBUG] Retrying single request %q", sub.singleRequest.DebugId)
			singleResp := sub.singleRequest.send()
			log.Printf("[DEBUG] Retried single request %q returned response: %v", sub.singleRequest.DebugId, singleResp)

			if singleResp.IsError() {
				singleResp.err = errwrap.Wrapf(
					"batch request and retry as single request failed - final error: {{err}}",
					singleResp.err)
			}
			sub.respCh <- singleResp
			close(sub.respCh)
		}
	} else {
		// Send result to all subscribers
		for _, sub := range batch.subscribers {
			sub.respCh <- resp
			close(sub.respCh)
		}
	}
}

// popBatch safely gets and removes a batch with given batchkey from the
// RequestBatcher's started batches.
func (b *RequestBatcher) popBatch(batchKey string) *startedBatch {
	b.Lock()
	defer b.Unlock()

	batch, ok := b.batches[batchKey]
	if !ok {
		log.Printf("[DEBUG] Batch with ID %q not found in batcher", batchKey)
		return nil
	}

	delete(b.batches, batchKey)
	return batch
}

func (batch *startedBatch) addRequest(newRequest *BatchRequest) (<-chan batchResponse, error) {
	log.Printf("[DEBUG] Adding batch request %q to existing batch %q", newRequest.DebugId, batch.batchKey)
	if batch.CombineF == nil {
		return nil, fmt.Errorf("Provider Error: unable to add request %q to batch %q with no CombineF", newRequest.DebugId, batch.batchKey)
	}
	newBody, err := batch.CombineF(batch.Body, newRequest.Body)
	if err != nil {
		return nil, fmt.Errorf("Provider Error: Unable to combine request %q data into existing batch %q: %v", newRequest.DebugId, batch.batchKey, err)
	}
	batch.Body = newBody

	log.Printf("[DEBUG] Added batch request %q to batch. New batch body: %v", newRequest.DebugId, batch.Body)

	respCh := make(chan batchResponse, 1)
	sub := batchSubscriber{
		singleRequest: newRequest,
		respCh:        respCh,
	}
	batch.subscribers = append(batch.subscribers, sub)
	return respCh, nil
}

func (req *BatchRequest) send() batchResponse {
	if req.SendF == nil {
		return batchResponse{
			err: fmt.Errorf("provider error: Batch request has no SendBatch function"),
		}
	}
	v, err := req.SendF(req.ResourceName, req.Body)
	return batchResponse{v, err}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/shared"
)

func containerFileHandler(d *Daemon, r *http.Request) Response {
	name := mux.Vars(r)["name"]
	c, err := containerLoadByName(d, name)
	if err != nil {
		return SmartError(err)
	}

	path := r.FormValue("path")
	if path == "" {
		return BadRequest(fmt.Errorf("missing path argument"))
	}

	switch r.Method {
	case "GET":
		return containerFileGet(c, path, r)
	case "POST":
		return containerFilePut(c, path, r)
	default:
		return NotFound
	}
}

func containerFileGet(c container, path string, r *http.Request) Response {
	/*
	 * Copy out of the ns to a temporary file, and then use that to serve
	 * the request from. This prevents us from having to worry about stuff
	 * like people breaking out of the container root by symlinks or
	 * ../../../s etc. in the path, since we can just rely on the kernel
	 * for correctness.
	 */
	temp, err := ioutil.TempFile("", "lxd_forkgetfile_")
	if err != nil {
		return InternalError(err)
	}
	defer temp.Close()

	// Pull the file from the container
	uid, gid, mode, type_, dirEnts, err := c.FilePull(path, temp.Name())
	if err != nil {
		return SmartError(err)
	}

	headers := map[string]string{
		"X-LXD-uid":  fmt.Sprintf("%d", uid),
		"X-LXD-gid":  fmt.Sprintf("%d", gid),
		"X-LXD-mode": fmt.Sprintf("%04o", mode),
		"X-LXD-type": type_,
	}

	if type_ == "file" {
		// Make a file response struct
		files := make([]fileResponseEntry, 1)
		files[0].identifier = filepath.Base(path)
		files[0].path = temp.Name()
		files[0].filename = filepath.Base(path)

		return FileResponse(r, files, headers, true)
	} else if type_ == "directory" {
		return SyncResponseHeaders(true, dirEnts, headers)
	} else {
		return InternalError(fmt.Errorf("bad file type %s", type_))
	}
}

func containerFilePut(c container, path string, r *http.Request) Response {
	// Extract file ownership and mode from headers
	uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)

	if type_ == "file" {
		// Write file content to a tempfile
		temp, err := ioutil.TempFile("", "lxd_forkputfile_")
		if err != nil {
			return InternalError(err)
		}
		defer func() {
			temp.Close()
			os.Remove(temp.Name())
		}()

		_, err = io.Copy(temp, r.Body)
		if err != nil {
			return InternalError(err)
		}

		// Transfer the file into the container
		err = c.FilePush(temp.Name(), path, uid, gid, mode)
		if err != nil {
			return InternalError(err)
		}

		return EmptySyncResponse
	} else if type_ == "directory" {
		err := c.FilePush("", path, uid, gid, mode)
		if err != nil {
			return InternalError(err)
		}
		return EmptySyncResponse
	} else {
		return InternalError(fmt.Errorf("bad file type %s", type_))
	}
}
<commit_msg>Cleanup leftover temp file<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/shared"
)

func containerFileHandler(d *Daemon, r *http.Request) Response {
	name := mux.Vars(r)["name"]
	c, err := containerLoadByName(d, name)
	if err != nil {
		return SmartError(err)
	}

	path := r.FormValue("path")
	if path == "" {
		return BadRequest(fmt.Errorf("missing path argument"))
	}

	switch r.Method {
	case "GET":
		return containerFileGet(c, path, r)
	case "POST":
		return containerFilePut(c, path, r)
	default:
		return NotFound
	}
}

func containerFileGet(c container, path string, r *http.Request) Response {
	/*
	 * Copy out of the ns to a temporary file, and then use that to serve
	 * the request from. This prevents us from having to worry about stuff
	 * like people breaking out of the container root by symlinks or
	 * ../../../s etc. in the path, since we can just rely on the kernel
	 * for correctness.
	 */
	temp, err := ioutil.TempFile("", "lxd_forkgetfile_")
	if err != nil {
		return InternalError(err)
	}
	defer temp.Close()

	// Pull the file from the container
	uid, gid, mode, type_, dirEnts, err := c.FilePull(path, temp.Name())
	if err != nil {
		os.Remove(temp.Name())
		return SmartError(err)
	}

	headers := map[string]string{
		"X-LXD-uid":  fmt.Sprintf("%d", uid),
		"X-LXD-gid":  fmt.Sprintf("%d", gid),
		"X-LXD-mode": fmt.Sprintf("%04o", mode),
		"X-LXD-type": type_,
	}

	if type_ == "file" {
		// Make a file response struct
		files := make([]fileResponseEntry, 1)
		files[0].identifier = filepath.Base(path)
		files[0].path = temp.Name()
		files[0].filename = filepath.Base(path)

		return FileResponse(r, files, headers, true)
	} else if type_ == "directory" {
		os.Remove(temp.Name())
		return SyncResponseHeaders(true, dirEnts, headers)
	} else {
		os.Remove(temp.Name())
		return InternalError(fmt.Errorf("bad file type %s", type_))
	}
}

func containerFilePut(c container, path string, r *http.Request) Response {
	// Extract file ownership and mode from headers
	uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)

	if type_ == "file" {
		// Write file content to a tempfile
		temp, err := ioutil.TempFile("", "lxd_forkputfile_")
		if err != nil {
			return InternalError(err)
		}
		defer func() {
			temp.Close()
			os.Remove(temp.Name())
		}()

		_, err = io.Copy(temp, r.Body)
		if err != nil {
			return InternalError(err)
		}

		// Transfer the file into the container
		err = c.FilePush(temp.Name(), path, uid, gid, mode)
		if err != nil {
			return InternalError(err)
		}

		return EmptySyncResponse
	} else if type_ == "directory" {
		err := c.FilePush("", path, uid, gid, mode)
		if err != nil {
			return InternalError(err)
		}
		return EmptySyncResponse
	} else {
		return InternalError(fmt.Errorf("bad file type %s", type_))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/node"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/rsync"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/shared"
)

func daemonStorageMount(s *state.State) error {
	var storageBackups string
	var storageImages string
	err := s.Node.Transaction(func(tx *db.NodeTx) error {
		nodeConfig, err := node.ConfigLoad(tx)
		if err != nil {
			return err
		}

		storageBackups = nodeConfig.StorageBackupsVolume()
		storageImages = nodeConfig.StorageImagesVolume()

		return nil
	})
	if err != nil {
		return err
	}

	mount := func(storageType string, source string) error {
		// Parse the source.
		fields := strings.Split(source, "/")
		if len(fields) != 2 {
			return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
		}

		poolName := fields[0]
		volumeName := fields[1]

		pool, err := storagePools.GetPoolByName(s, poolName)
		if err != nil {
			return err
		}

		// Mount volume.
		_, err = pool.MountCustomVolume(project.Default, volumeName, nil)
		if err != nil {
			return errors.Wrapf(err, "Failed to mount storage volume %q", source)
		}

		return nil
	}

	if storageBackups != "" {
		err := mount("backups", storageBackups)
		if err != nil {
			return errors.Wrap(err, "Failed to mount backups storage")
		}
	}

	if storageImages != "" {
		err := mount("images", storageImages)
		if err != nil {
			return errors.Wrap(err, "Failed to mount images storage")
		}
	}

	return nil
}

func daemonStorageUsed(s *state.State, poolName string, volumeName string) (bool, error) {
	var storageBackups string
	var storageImages string
	err := s.Node.Transaction(func(tx *db.NodeTx) error {
		nodeConfig, err := node.ConfigLoad(tx)
		if err != nil {
			return err
		}

		storageBackups = nodeConfig.StorageBackupsVolume()
		storageImages = nodeConfig.StorageImagesVolume()

		return nil
	})
	if err != nil {
		return false, err
	}

	fullName := fmt.Sprintf("%s/%s", poolName, volumeName)
	if storageBackups == fullName || storageImages == fullName {
		return true, nil
	}

	return false, nil
}

func daemonStorageValidate(s *state.State, target string) error {
	// Check syntax.
	if target == "" {
		return nil
	}

	fields := strings.Split(target, "/")
	if len(fields) != 2 {
		return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
	}

	poolName := fields[0]
	volumeName := fields[1]

	// Validate pool exists.
	poolID, dbPool, err := s.Cluster.StoragePoolGet(poolName)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage pool %q", poolName)
	}

	// Validate pool driver (can't be CEPH or CEPHFS).
	if dbPool.Driver == "ceph" || dbPool.Driver == "cephfs" {
		return fmt.Errorf("Server storage volumes cannot be stored on Ceph")
	}

	// Confirm volume exists.
	_, _, err = s.Cluster.StoragePoolNodeVolumeGetTypeByProject(project.Default, volumeName, db.StoragePoolVolumeTypeCustom, poolID)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage volume %q", target)
	}

	snapshots, err := s.Cluster.StoragePoolVolumeSnapshotsGetType(project.Default, volumeName, db.StoragePoolVolumeTypeCustom, poolID)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage volume snapshots %q", target)
	}

	if len(snapshots) != 0 {
		return fmt.Errorf("Storage volumes for use by LXD itself cannot have snapshots")
	}

	pool, err := storagePools.GetPoolByName(s, poolName)
	if err != nil {
		return err
	}

	// Mount volume.
	ourMount, err := pool.MountCustomVolume(project.Default, volumeName, nil)
	if err != nil {
		return errors.Wrapf(err, "Failed to mount storage volume %q", target)
	}
	if ourMount {
		defer pool.UnmountCustomVolume(project.Default, volumeName, nil)
	}

	// Validate volume is empty (ignore lost+found).
	volStorageName := project.StorageVolume(project.Default, volumeName)
	mountpoint := storageDrivers.GetVolumeMountPath(poolName, storageDrivers.VolumeTypeCustom, volStorageName)

	entries, err := ioutil.ReadDir(mountpoint)
	if err != nil {
		return errors.Wrapf(err, "Failed to list %q", mountpoint)
	}

	for _, entry := range entries {
		entryName := entry.Name()

		if entryName == "lost+found" {
			continue
		}

		return fmt.Errorf("Storage volume %q isn't empty", target)
	}

	return nil
}

func daemonStorageMove(s *state.State, storageType string, target string) error {
	destPath := shared.VarPath(storageType)

	// Track down the current storage.
	var sourcePool string
	var sourceVolume string

	sourcePath, err := os.Readlink(destPath)
	if err != nil {
		sourcePath = destPath
	} else {
		fields := strings.Split(sourcePath, "/")
		sourcePool = fields[len(fields)-3]
		sourceVolume = fields[len(fields)-1]
	}

	moveContent := func(source string, target string) error {
		// Copy the content.
		_, err := rsync.LocalCopy(source, target, "", false)
		if err != nil {
			return err
		}

		// Remove the source content.
		entries, err := ioutil.ReadDir(source)
		if err != nil {
			return err
		}

		for _, entry := range entries {
			err := os.RemoveAll(filepath.Join(source, entry.Name()))
			if err != nil {
				return err
			}
		}

		return nil
	}

	// Deal with unsetting.
	if target == "" {
		// Things already look correct.
		if sourcePath == destPath {
			return nil
		}

		// Remove the symlink.
		err = os.Remove(destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to delete storage symlink at %q", destPath)
		}

		// Re-create as a directory.
		err = os.MkdirAll(destPath, 0700)
		if err != nil {
			return errors.Wrapf(err, "Failed to create directory %q", destPath)
		}

		// Move the data across.
		err = moveContent(sourcePath, destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
		}

		pool, err := storagePools.GetPoolByName(s, sourcePool)
		if err != nil {
			return err
		}

		// Unmount old volume.
		projectName, sourceVolumeName := project.StorageVolumeParts(sourceVolume)
		_, err = pool.UnmountCustomVolume(projectName, sourceVolumeName, nil)
		if err != nil {
			return errors.Wrapf(err, `Failed to umount storage volume "%s/%s"`, sourcePool, sourceVolumeName)
		}

		return nil
	}

	// Parse the target.
	fields := strings.Split(target, "/")
	if len(fields) != 2 {
		return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
	}

	poolName := fields[0]
	volumeName := fields[1]

	pool, err := storagePools.GetPoolByName(s, poolName)
	if err != nil {
		return err
	}

	// Mount volume.
	_, err = pool.MountCustomVolume(project.Default, volumeName, nil)
	if err != nil {
		return errors.Wrapf(err, "Failed to mount storage volume %q", target)
	}

	// Set ownership & mode.
	volStorageName := project.StorageVolume(project.Default, volumeName)
	mountpoint := storageDrivers.GetVolumeMountPath(poolName, storageDrivers.VolumeTypeCustom, volStorageName)
	destPath = mountpoint

	err = os.Chmod(mountpoint, 0700)
	if err != nil {
		return errors.Wrapf(err, "Failed to set permissions on %q", mountpoint)
	}

	err = os.Chown(mountpoint, 0, 0)
	if err != nil {
		return errors.Wrapf(err, "Failed to set ownership on %q", mountpoint)
	}

	// Handle changes.
	if sourcePath != shared.VarPath(storageType) {
		// Remove the symlink.
		err := os.Remove(shared.VarPath(storageType))
		if err != nil {
			return errors.Wrapf(err, "Failed to remove the new symlink at %q", shared.VarPath(storageType))
		}

		// Create the new symlink.
		err = os.Symlink(destPath, shared.VarPath(storageType))
		if err != nil {
			return errors.Wrapf(err, "Failed to create the new symlink at %q", shared.VarPath(storageType))
		}

		// Move the data across.
		err = moveContent(sourcePath, destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
		}

		pool, err := storagePools.GetPoolByName(s, sourcePool)
		if err != nil {
			return err
		}

		// Unmount old volume.
		_, err = pool.UnmountCustomVolume(project.Default, sourceVolume, nil)
		if err != nil {
			return errors.Wrapf(err, `Failed to umount storage volume "%s/%s"`, sourcePool, sourceVolume)
		}

		return nil
	}

	sourcePath = shared.VarPath(storageType) + ".temp"

	// Rename the existing storage.
	err = os.Rename(shared.VarPath(storageType), sourcePath)
	if err != nil {
		return errors.Wrapf(err, "Failed to rename existing storage %q", shared.VarPath(storageType))
	}

	// Create the new symlink.
	err = os.Symlink(destPath, shared.VarPath(storageType))
	if err != nil {
		return errors.Wrapf(err, "Failed to create the new symlink at %q", shared.VarPath(storageType))
	}

	// Move the data across.
	err = moveContent(sourcePath, destPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
	}

	// Remove the old data.
	err = os.RemoveAll(sourcePath)
	if err != nil {
		return errors.Wrapf(err, "Failed to cleanup old directory %q", sourcePath)
	}

	return nil
}
<commit_msg>lxd/daemon/storage: Removes daemonStorageUsed function<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/node"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/rsync"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/shared"
)

func daemonStorageMount(s *state.State) error {
	var storageBackups string
	var storageImages string
	err := s.Node.Transaction(func(tx *db.NodeTx) error {
		nodeConfig, err := node.ConfigLoad(tx)
		if err != nil {
			return err
		}

		storageBackups = nodeConfig.StorageBackupsVolume()
		storageImages = nodeConfig.StorageImagesVolume()

		return nil
	})
	if err != nil {
		return err
	}

	mount := func(storageType string, source string) error {
		// Parse the source.
		fields := strings.Split(source, "/")
		if len(fields) != 2 {
			return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
		}

		poolName := fields[0]
		volumeName := fields[1]

		pool, err := storagePools.GetPoolByName(s, poolName)
		if err != nil {
			return err
		}

		// Mount volume.
		_, err = pool.MountCustomVolume(project.Default, volumeName, nil)
		if err != nil {
			return errors.Wrapf(err, "Failed to mount storage volume %q", source)
		}

		return nil
	}

	if storageBackups != "" {
		err := mount("backups", storageBackups)
		if err != nil {
			return errors.Wrap(err, "Failed to mount backups storage")
		}
	}

	if storageImages != "" {
		err := mount("images", storageImages)
		if err != nil {
			return errors.Wrap(err, "Failed to mount images storage")
		}
	}

	return nil
}

func daemonStorageValidate(s *state.State, target string) error {
	// Check syntax.
	if target == "" {
		return nil
	}

	fields := strings.Split(target, "/")
	if len(fields) != 2 {
		return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
	}

	poolName := fields[0]
	volumeName := fields[1]

	// Validate pool exists.
	poolID, dbPool, err := s.Cluster.StoragePoolGet(poolName)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage pool %q", poolName)
	}

	// Validate pool driver (can't be CEPH or CEPHFS).
	if dbPool.Driver == "ceph" || dbPool.Driver == "cephfs" {
		return fmt.Errorf("Server storage volumes cannot be stored on Ceph")
	}

	// Confirm volume exists.
	_, _, err = s.Cluster.StoragePoolNodeVolumeGetTypeByProject(project.Default, volumeName, db.StoragePoolVolumeTypeCustom, poolID)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage volume %q", target)
	}

	snapshots, err := s.Cluster.StoragePoolVolumeSnapshotsGetType(project.Default, volumeName, db.StoragePoolVolumeTypeCustom, poolID)
	if err != nil {
		return errors.Wrapf(err, "Unable to load storage volume snapshots %q", target)
	}

	if len(snapshots) != 0 {
		return fmt.Errorf("Storage volumes for use by LXD itself cannot have snapshots")
	}

	pool, err := storagePools.GetPoolByName(s, poolName)
	if err != nil {
		return err
	}

	// Mount volume.
	ourMount, err := pool.MountCustomVolume(project.Default, volumeName, nil)
	if err != nil {
		return errors.Wrapf(err, "Failed to mount storage volume %q", target)
	}
	if ourMount {
		defer pool.UnmountCustomVolume(project.Default, volumeName, nil)
	}

	// Validate volume is empty (ignore lost+found).
	volStorageName := project.StorageVolume(project.Default, volumeName)
	mountpoint := storageDrivers.GetVolumeMountPath(poolName, storageDrivers.VolumeTypeCustom, volStorageName)

	entries, err := ioutil.ReadDir(mountpoint)
	if err != nil {
		return errors.Wrapf(err, "Failed to list %q", mountpoint)
	}

	for _, entry := range entries {
		entryName := entry.Name()

		if entryName == "lost+found" {
			continue
		}

		return fmt.Errorf("Storage volume %q isn't empty", target)
	}

	return nil
}

func daemonStorageMove(s *state.State, storageType string, target string) error {
	destPath := shared.VarPath(storageType)

	// Track down the current storage.
	var sourcePool string
	var sourceVolume string

	sourcePath, err := os.Readlink(destPath)
	if err != nil {
		sourcePath = destPath
	} else {
		fields := strings.Split(sourcePath, "/")
		sourcePool = fields[len(fields)-3]
		sourceVolume = fields[len(fields)-1]
	}

	moveContent := func(source string, target string) error {
		// Copy the content.
		_, err := rsync.LocalCopy(source, target, "", false)
		if err != nil {
			return err
		}

		// Remove the source content.
		entries, err := ioutil.ReadDir(source)
		if err != nil {
			return err
		}

		for _, entry := range entries {
			err := os.RemoveAll(filepath.Join(source, entry.Name()))
			if err != nil {
				return err
			}
		}

		return nil
	}

	// Deal with unsetting.
	if target == "" {
		// Things already look correct.
		if sourcePath == destPath {
			return nil
		}

		// Remove the symlink.
		err = os.Remove(destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to delete storage symlink at %q", destPath)
		}

		// Re-create as a directory.
		err = os.MkdirAll(destPath, 0700)
		if err != nil {
			return errors.Wrapf(err, "Failed to create directory %q", destPath)
		}

		// Move the data across.
		err = moveContent(sourcePath, destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
		}

		pool, err := storagePools.GetPoolByName(s, sourcePool)
		if err != nil {
			return err
		}

		// Unmount old volume.
		projectName, sourceVolumeName := project.StorageVolumeParts(sourceVolume)
		_, err = pool.UnmountCustomVolume(projectName, sourceVolumeName, nil)
		if err != nil {
			return errors.Wrapf(err, `Failed to umount storage volume "%s/%s"`, sourcePool, sourceVolumeName)
		}

		return nil
	}

	// Parse the target.
	fields := strings.Split(target, "/")
	if len(fields) != 2 {
		return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
	}

	poolName := fields[0]
	volumeName := fields[1]

	pool, err := storagePools.GetPoolByName(s, poolName)
	if err != nil {
		return err
	}

	// Mount volume.
	_, err = pool.MountCustomVolume(project.Default, volumeName, nil)
	if err != nil {
		return errors.Wrapf(err, "Failed to mount storage volume %q", target)
	}

	// Set ownership & mode.
	volStorageName := project.StorageVolume(project.Default, volumeName)
	mountpoint := storageDrivers.GetVolumeMountPath(poolName, storageDrivers.VolumeTypeCustom, volStorageName)
	destPath = mountpoint

	err = os.Chmod(mountpoint, 0700)
	if err != nil {
		return errors.Wrapf(err, "Failed to set permissions on %q", mountpoint)
	}

	err = os.Chown(mountpoint, 0, 0)
	if err != nil {
		return errors.Wrapf(err, "Failed to set ownership on %q", mountpoint)
	}

	// Handle changes.
	if sourcePath != shared.VarPath(storageType) {
		// Remove the symlink.
		err := os.Remove(shared.VarPath(storageType))
		if err != nil {
			return errors.Wrapf(err, "Failed to remove the new symlink at %q", shared.VarPath(storageType))
		}

		// Create the new symlink.
		err = os.Symlink(destPath, shared.VarPath(storageType))
		if err != nil {
			return errors.Wrapf(err, "Failed to create the new symlink at %q", shared.VarPath(storageType))
		}

		// Move the data across.
		err = moveContent(sourcePath, destPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
		}

		pool, err := storagePools.GetPoolByName(s, sourcePool)
		if err != nil {
			return err
		}

		// Unmount old volume.
		_, err = pool.UnmountCustomVolume(project.Default, sourceVolume, nil)
		if err != nil {
			return errors.Wrapf(err, `Failed to umount storage volume "%s/%s"`, sourcePool, sourceVolume)
		}

		return nil
	}

	sourcePath = shared.VarPath(storageType) + ".temp"

	// Rename the existing storage.
	err = os.Rename(shared.VarPath(storageType), sourcePath)
	if err != nil {
		return errors.Wrapf(err, "Failed to rename existing storage %q", shared.VarPath(storageType))
	}

	// Create the new symlink.
	err = os.Symlink(destPath, shared.VarPath(storageType))
	if err != nil {
		return errors.Wrapf(err, "Failed to create the new symlink at %q", shared.VarPath(storageType))
	}

	// Move the data across.
	err = moveContent(sourcePath, destPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to move data over to directory %q", destPath)
	}

	// Remove the old data.
	err = os.RemoveAll(sourcePath)
	if err != nil {
		return errors.Wrapf(err, "Failed to cleanup old directory %q", sourcePath)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/shared/api"
)

func (c *cmdInit) RunDump(d lxd.InstanceServer) error {
	currentServer, _, err := d.GetServer()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	var config initDataNode
	config.Config = currentServer.Config

	networks, err := d.GetNetworks()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	for _, network := range networks {
		// Only list managed networks
		if !network.Managed {
			continue
		}
		networksPost := api.NetworksPost{}
		networksPost.Config = network.Config
		networksPost.Description = network.Description
		networksPost.Name = network.Name
		networksPost.Type = network.Type

		config.Networks = append(config.Networks, networksPost)
	}

	storagePools, err := d.GetStoragePools()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	for _, storagePool := range storagePools {
		storagePoolsPost := api.StoragePoolsPost{}
		storagePoolsPost.Config = storagePool.Config
		storagePoolsPost.Description = storagePool.Description
		storagePoolsPost.Name = storagePool.Name
		storagePoolsPost.Driver = storagePool.Driver

		config.StoragePools = append(config.StoragePools, storagePoolsPost)
	}

	profiles, err := d.GetProfiles()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	for _, profile := range profiles {
		profilesPost := api.ProfilesPost{}
		profilesPost.Config = profile.Config
		profilesPost.Description = profile.Description
		profilesPost.Devices = profile.Devices
		profilesPost.Name = profile.Name

		config.Profiles = append(config.Profiles, profilesPost)
	}

	out, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	fmt.Printf("%s\n", out)

	return nil
}
<commit_msg>lxd/main/init/dump: Updates RunDump to use internalClusterPostNetwork<commit_after>package main

import (
	"fmt"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/shared/api"
)

func (c *cmdInit) RunDump(d lxd.InstanceServer) error {
	currentServer, _, err := d.GetServer()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	var config initDataNode
	config.Config = currentServer.Config

	// Only retrieve networks in the default project as the preseed format doesn't support creating
	// projects at this time.
	networks, err := d.UseProject(project.Default).GetNetworks()
	if err != nil {
		return errors.Wrapf(err, "Failed to retrieve current server network configuration for project %q", project.Default)
	}

	for _, network := range networks {
		// Only list managed networks.
		if !network.Managed {
			continue
		}

		networksPost := internalClusterPostNetwork{}
		networksPost.Config = network.Config
		networksPost.Description = network.Description
		networksPost.Name = network.Name
		networksPost.Type = network.Type
		networksPost.Project = project.Default

		config.Networks = append(config.Networks, networksPost)
	}

	storagePools, err := d.GetStoragePools()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	for _, storagePool := range storagePools {
		storagePoolsPost := api.StoragePoolsPost{}
		storagePoolsPost.Config = storagePool.Config
		storagePoolsPost.Description = storagePool.Description
		storagePoolsPost.Name = storagePool.Name
		storagePoolsPost.Driver = storagePool.Driver

		config.StoragePools = append(config.StoragePools, storagePoolsPost)
	}

	profiles, err := d.GetProfiles()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	for _, profile := range profiles {
		profilesPost := api.ProfilesPost{}
		profilesPost.Config = profile.Config
		profilesPost.Description = profile.Description
		profilesPost.Devices = profile.Devices
		profilesPost.Name = profile.Name

		config.Profiles = append(config.Profiles, profilesPost)
	}

	out, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current server configuration")
	}

	fmt.Printf("%s\n", out)

	return nil
}
<|endoftext|>


<commit_before>package management

import (
	"fmt"
)

type Linkable interface {
	Link() map[string]map[string]interface{}
}

type EntryFields map[string]map[string]interface{}

type NewEntry struct {
	Fields EntryFields `json:"fields"`
}

// Validate validates the entry
func (c *NewEntry) Validate() error {
	if c.Fields == nil || len(c.Fields) == 0 {
		return fmt.Errorf("NewEntry.Fields cannot be empty!")
	}

	return nil
}

// Entry represent textual content in a space. An entry's data adheres to a
// certain content type.
type Entry struct {
	System `json:"sys"`
	Fields EntryFields `json:"fields"`
}

// Validate will validate the entry. An error is returned if the entry
// is not  valid.
func (c *Entry) Validate() error {
	if c.Space == nil || c.Space.ID == "" {
		return fmt.Errorf("Entry must have a valid Space associated with it!")
	}

	if c.System.ID == "" {
		return fmt.Errorf("Entry.System.ID cannot be empty!")
	}

	if c.Fields == nil || len(c.Fields) == 0 {
		return fmt.Errorf("Entry.Fields cannot be empty!")
	}

	return nil
}

// Link represents a link to the Entry and implements the Linkable interface
func (c *Entry) Link() map[string]map[string]interface{} {
	return map[string]map[string]interface{}{
		"sys": map[string]interface{}{
			"id":       c.ID,
			"linkType": "Entry",
			"type":     LinkType,
		},
	}
}

// QueryEntries returns all entries for the given space and parameters.
func (c *Client) QueryEntries(spaceID string, params map[string]string, limit int, offset int) (entries []*Entry, pagination *Pagination, err error) {
	if spaceID == "" {
		return nil, nil, fmt.Errorf("FetchEntries failed. Space identifier is not valid!")
	}

	if limit < 0 {
		return nil, nil, fmt.Errorf("FetchEntries failed. Limit must be greater than 0")
	}

	if limit > 100 {
		limit = 100
	}

	c.rl.Wait()

	type entriesResponse struct {
		*Pagination
		Items []*Entry `json:"items"`
	}

	results := new(entriesResponse)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries", spaceID)
	req, err := c.sling.New().
		Get(path).
		Request()

	if err != nil {
		return
	}

	// Add query parameters
	q := req.URL.Query()
	for k, v := range params {
		q.Set(k, v)
	}

	q.Set("skip", fmt.Sprintf("%v", offset))
	q.Set("limit", fmt.Sprintf("%v", limit))
	req.URL.RawQuery = q.Encode()

	// Perform request
	_, err = c.sling.Do(req, results, contentfulError)

	return results.Items, results.Pagination, handleError(err, contentfulError)
}

// FetchEntry returns a single entry for the given space and entry identifier
func (c *Client) FetchEntry(spaceID string, entryID string) (entry *Entry, err error) {
	if spaceID == "" || entryID == "" {
		err = fmt.Errorf("FetchContentType failed. Invalid spaceID or contentTypeID.")
		return
	}

	c.rl.Wait()

	entry = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", spaceID, entryID)
	_, err = c.sling.New().Get(path).Receive(entry, contentfulError)

	return entry, handleError(err, contentfulError)
}

// CreateEntry will create a new entry with an ID specified by the user or
// generated by the system
func (c *Client) CreateEntry(entry *NewEntry, contentType *ContentType) (created *Entry, err error) {
	if entry == nil || contentType == nil {
		err = fmt.Errorf("CreateEntry failed, entry and contentType cannot be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	if err = contentType.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	created = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries", contentType.Space.ID)
	_, err = c.sling.New().
		Post(path).
		Set("X-Contentful-Content-Type", contentType.ID).
		BodyJSON(entry).
		Receive(created, contentfulError)

	return created, handleError(err, contentfulError)
}

// UpdateEntry will update the specified entry with any changes that you have
// made.
func (c *Client) UpdateEntry(entry *Entry) (updated *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("CreateEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Put(path).
		Set("X-Contentful-Version", fmt.Sprintf("%v", entry.System.Version)).
		BodyJSON(entry).
		Receive(updated, contentfulError)

	return updated, handleError(err, contentfulError)
}

// DeleteEntry will delete the specified entry
func (c *Client) DeleteEntry(entryID string, spaceID string) (err error) {

	c.rl.Wait()

	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", spaceID, entryID)
	_, err = c.sling.New().
		Delete(path).
		Receive(nil, contentfulError)

	return handleError(err, contentfulError)
}

// PublishEntry makes the entry available via the Content Delivery API
func (c *Client) PublishEntry(entry *Entry) (published *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("PublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	published = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/published", entry.Space.ID, entry.System.ID)
	fmt.Println("path:", path)
	_, err = c.sling.New().
		Put(path).
		Set("X-Contentful-Version", fmt.Sprintf("%v", entry.System.Version)).
		Receive(published, contentfulError)

	return published, handleError(err, contentfulError)
}

// UnpublishEntry makes the entry unavailable via the Content Delivery API
func (c *Client) UnpublishEntry(entry *Entry) (unpublished *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("UnpublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	unpublished = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/published", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Delete(path).
		Receive(unpublished, contentfulError)

	return unpublished, handleError(err, contentfulError)
}

// ArchiveEntry will archive the specified entry. An entry can only be archived
// when it's not published.
func (c *Client) ArchiveEntry(entry *Entry) (archived *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("PublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	archived = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/archived", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Put(path).
		Receive(archived, contentfulError)

	return archived, handleError(err, contentfulError)
}

// UnarchiveEntry unarchives the specified entry.
func (c *Client) UnarchiveEntry(entry *Entry) (unarchived *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("UnpublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	unarchived = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/archived", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Delete(path).
		Receive(unarchived, contentfulError)

	return unarchived, handleError(err, contentfulError)
}
<commit_msg>Adding missing entry initialization to UpdateEntry<commit_after>package management

import (
	"fmt"
)

type Linkable interface {
	Link() map[string]map[string]interface{}
}

type EntryFields map[string]map[string]interface{}

type NewEntry struct {
	Fields EntryFields `json:"fields"`
}

// Validate validates the entry
func (c *NewEntry) Validate() error {
	if c.Fields == nil || len(c.Fields) == 0 {
		return fmt.Errorf("NewEntry.Fields cannot be empty!")
	}

	return nil
}

// Entry represent textual content in a space. An entry's data adheres to a
// certain content type.
type Entry struct {
	System `json:"sys"`
	Fields EntryFields `json:"fields"`
}

// Validate will validate the entry. An error is returned if the entry
// is not  valid.
func (c *Entry) Validate() error {
	if c.Space == nil || c.Space.ID == "" {
		return fmt.Errorf("Entry must have a valid Space associated with it!")
	}

	if c.System.ID == "" {
		return fmt.Errorf("Entry.System.ID cannot be empty!")
	}

	if c.Fields == nil || len(c.Fields) == 0 {
		return fmt.Errorf("Entry.Fields cannot be empty!")
	}

	return nil
}

// Link represents a link to the Entry and implements the Linkable interface
func (c *Entry) Link() map[string]map[string]interface{} {
	return map[string]map[string]interface{}{
		"sys": map[string]interface{}{
			"id":       c.ID,
			"linkType": "Entry",
			"type":     LinkType,
		},
	}
}

// QueryEntries returns all entries for the given space and parameters.
func (c *Client) QueryEntries(spaceID string, params map[string]string, limit int, offset int) (entries []*Entry, pagination *Pagination, err error) {
	if spaceID == "" {
		return nil, nil, fmt.Errorf("FetchEntries failed. Space identifier is not valid!")
	}

	if limit < 0 {
		return nil, nil, fmt.Errorf("FetchEntries failed. Limit must be greater than 0")
	}

	if limit > 100 {
		limit = 100
	}

	c.rl.Wait()

	type entriesResponse struct {
		*Pagination
		Items []*Entry `json:"items"`
	}

	results := new(entriesResponse)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries", spaceID)
	req, err := c.sling.New().
		Get(path).
		Request()

	if err != nil {
		return
	}

	// Add query parameters
	q := req.URL.Query()
	for k, v := range params {
		q.Set(k, v)
	}

	q.Set("skip", fmt.Sprintf("%v", offset))
	q.Set("limit", fmt.Sprintf("%v", limit))
	req.URL.RawQuery = q.Encode()

	// Perform request
	_, err = c.sling.Do(req, results, contentfulError)

	return results.Items, results.Pagination, handleError(err, contentfulError)
}

// FetchEntry returns a single entry for the given space and entry identifier
func (c *Client) FetchEntry(spaceID string, entryID string) (entry *Entry, err error) {
	if spaceID == "" || entryID == "" {
		err = fmt.Errorf("FetchContentType failed. Invalid spaceID or contentTypeID.")
		return
	}

	c.rl.Wait()

	entry = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", spaceID, entryID)
	_, err = c.sling.New().Get(path).Receive(entry, contentfulError)

	return entry, handleError(err, contentfulError)
}

// CreateEntry will create a new entry with an ID specified by the user or
// generated by the system
func (c *Client) CreateEntry(entry *NewEntry, contentType *ContentType) (created *Entry, err error) {
	if entry == nil || contentType == nil {
		err = fmt.Errorf("CreateEntry failed, entry and contentType cannot be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	if err = contentType.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	created = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries", contentType.Space.ID)
	_, err = c.sling.New().
		Post(path).
		Set("X-Contentful-Content-Type", contentType.ID).
		BodyJSON(entry).
		Receive(created, contentfulError)

	return created, handleError(err, contentfulError)
}

// UpdateEntry will update the specified entry with any changes that you have
// made.
func (c *Client) UpdateEntry(entry *Entry) (updated *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("CreateEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	updated = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Put(path).
		Set("X-Contentful-Version", fmt.Sprintf("%v", entry.System.Version)).
		BodyJSON(entry).
		Receive(updated, contentfulError)

	return updated, handleError(err, contentfulError)
}

// DeleteEntry will delete the specified entry
func (c *Client) DeleteEntry(entryID string, spaceID string) (err error) {

	c.rl.Wait()

	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v", spaceID, entryID)
	_, err = c.sling.New().
		Delete(path).
		Receive(nil, contentfulError)

	return handleError(err, contentfulError)
}

// PublishEntry makes the entry available via the Content Delivery API
func (c *Client) PublishEntry(entry *Entry) (published *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("PublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	published = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/published", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Put(path).
		Set("X-Contentful-Version", fmt.Sprintf("%v", entry.System.Version)).
		Receive(published, contentfulError)

	return published, handleError(err, contentfulError)
}

// UnpublishEntry makes the entry unavailable via the Content Delivery API
func (c *Client) UnpublishEntry(entry *Entry) (unpublished *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("UnpublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	unpublished = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/published", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Delete(path).
		Receive(unpublished, contentfulError)

	return unpublished, handleError(err, contentfulError)
}

// ArchiveEntry will archive the specified entry. An entry can only be archived
// when it's not published.
func (c *Client) ArchiveEntry(entry *Entry) (archived *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("PublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	archived = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/archived", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Put(path).
		Receive(archived, contentfulError)

	return archived, handleError(err, contentfulError)
}

// UnarchiveEntry unarchives the specified entry.
func (c *Client) UnarchiveEntry(entry *Entry) (unarchived *Entry, err error) {
	if entry == nil {
		err = fmt.Errorf("UnpublishEntry failed. Entry must not be nil!")
		return
	}

	if err = entry.Validate(); err != nil {
		return
	}

	c.rl.Wait()

	unarchived = new(Entry)
	contentfulError := new(ContentfulError)
	path := fmt.Sprintf("spaces/%v/entries/%v/archived", entry.Space.ID, entry.System.ID)
	_, err = c.sling.New().
		Delete(path).
		Receive(unarchived, contentfulError)

	return unarchived, handleError(err, contentfulError)
}
<|endoftext|>