<commit_before>/*
Copyright 2018 The Ceph-CSI Authors.

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 rbd

import (
	csicommon "github.com/ceph/ceph-csi/internal/csi-common"
	"github.com/ceph/ceph-csi/internal/journal"
	"github.com/ceph/ceph-csi/internal/util"

	"github.com/container-storage-interface/spec/lib/go/csi"
	klog "k8s.io/klog/v2"
	"k8s.io/utils/mount"
)

const (
	// volIDVersion is the version number of volume ID encoding scheme
	volIDVersion uint16 = 1
)

// Driver contains the default identity,node and controller struct.
type Driver struct {
	cd *csicommon.CSIDriver

	ids *IdentityServer
	ns  *NodeServer
	cs  *ControllerServer
}

var (

	// CSIInstanceID is the instance ID that is unique to an instance of CSI, used when sharing
	// ceph clusters across CSI instances, to differentiate omap names per CSI instance
	CSIInstanceID = "default"

	// volJournal and snapJournal are used to maintain RADOS based journals for CO generated
	// VolumeName to backing RBD images
	volJournal  *journal.Config
	snapJournal *journal.Config
	// rbdHardMaxCloneDepth is the hard limit for maximum number of nested volume clones that are taken before a flatten occurs
	rbdHardMaxCloneDepth uint

	// rbdSoftMaxCloneDepth is the soft limit for maximum number of nested volume clones that are taken before a flatten occurs
	rbdSoftMaxCloneDepth uint
	maxSnapshotsOnImage  uint
	skipForceFlatten     bool
)

// NewDriver returns new rbd driver.
func NewDriver() *Driver {
	return &Driver{}
}

// NewIdentityServer initialize a identity server for rbd CSI driver.
func NewIdentityServer(d *csicommon.CSIDriver) *IdentityServer {
	return &IdentityServer{
		DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
	}
}

// NewControllerServer initialize a controller server for rbd CSI driver.
func NewControllerServer(d *csicommon.CSIDriver) *ControllerServer {
	return &ControllerServer{
		DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
		VolumeLocks:             util.NewVolumeLocks(),
		SnapshotLocks:           util.NewVolumeLocks(),
		OperationLocks:          util.NewOperationLock(),
	}
}

// NewNodeServer initialize a node server for rbd CSI driver.
func NewNodeServer(d *csicommon.CSIDriver, t string, topology map[string]string) (*NodeServer, error) {
	mounter := mount.New("")
	return &NodeServer{
		DefaultNodeServer: csicommon.NewDefaultNodeServer(d, t, topology),
		mounter:           mounter,
		VolumeLocks:       util.NewVolumeLocks(),
	}, nil
}

// Run start a non-blocking grpc controller,node and identityserver for
// rbd CSI driver which can serve multiple parallel requests.
func (r *Driver) Run(conf *util.Config) {
	var err error
	var topology map[string]string

	// Create ceph.conf for use with CLI commands
	if err = util.WriteCephConfig(); err != nil {
		klog.Fatalf("failed to write ceph configuration file (%v)", err)
	}

	// Use passed in instance ID, if provided for omap suffix naming
	if conf.InstanceID != "" {
		CSIInstanceID = conf.InstanceID
	}

	// update clone soft and hard limit
	rbdHardMaxCloneDepth = conf.RbdHardMaxCloneDepth
	rbdSoftMaxCloneDepth = conf.RbdSoftMaxCloneDepth
	skipForceFlatten = conf.SkipForceFlatten
	maxSnapshotsOnImage = conf.MaxSnapshotsOnImage
	// Create instances of the volume and snapshot journal
	volJournal = journal.NewCSIVolumeJournal(CSIInstanceID)
	snapJournal = journal.NewCSISnapshotJournal(CSIInstanceID)

	// Initialize default library driver
	r.cd = csicommon.NewCSIDriver(conf.DriverName, util.DriverVersion, conf.NodeID)
	if r.cd == nil {
		klog.Fatalln("Failed to initialize CSI Driver.")
	}
	if conf.IsControllerServer || !conf.IsNodeServer {
		r.cd.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
			csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
			csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
		})
		// We only support the multi-writer option when using block, but it's a supported capability for the plugin in general
		// In addition, we want to add the remaining modes like MULTI_NODE_READER_ONLY,
		// MULTI_NODE_SINGLE_WRITER etc, but need to do some verification of RO modes first
		// will work those as follow up features
		r.cd.AddVolumeCapabilityAccessModes(
			[]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
				csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER})
	}

	// Create GRPC servers
	r.ids = NewIdentityServer(r.cd)

	if conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			klog.Fatalln(err)
		}
		r.ns, err = NewNodeServer(r.cd, conf.Vtype, topology)
		if err != nil {
			klog.Fatalf("failed to start node server, err %v\n", err)
		}
	}

	if conf.IsControllerServer {
		r.cs = NewControllerServer(r.cd)
	}
	if !conf.IsControllerServer && !conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			klog.Fatalln(err)
		}
		r.ns, err = NewNodeServer(r.cd, conf.Vtype, topology)
		if err != nil {
			klog.Fatalf("failed to start node server, err %v\n", err)
		}
		r.cs = NewControllerServer(r.cd)
	}

	s := csicommon.NewNonBlockingGRPCServer()
	s.Start(conf.Endpoint, conf.HistogramOption, r.ids, r.cs, r.ns, conf.EnableGRPCMetrics)
	if conf.EnableGRPCMetrics {
		klog.Warning("EnableGRPCMetrics is deprecated")
		go util.StartMetricsServer(conf)
	}
	s.Wait()
}
<commit_msg>rbd: replace klog with util logger in driver.go<commit_after>/*
Copyright 2018 The Ceph-CSI Authors.

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 rbd

import (
	csicommon "github.com/ceph/ceph-csi/internal/csi-common"
	"github.com/ceph/ceph-csi/internal/journal"
	"github.com/ceph/ceph-csi/internal/util"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"k8s.io/utils/mount"
)

const (
	// volIDVersion is the version number of volume ID encoding scheme
	volIDVersion uint16 = 1
)

// Driver contains the default identity,node and controller struct.
type Driver struct {
	cd *csicommon.CSIDriver

	ids *IdentityServer
	ns  *NodeServer
	cs  *ControllerServer
}

var (

	// CSIInstanceID is the instance ID that is unique to an instance of CSI, used when sharing
	// ceph clusters across CSI instances, to differentiate omap names per CSI instance
	CSIInstanceID = "default"

	// volJournal and snapJournal are used to maintain RADOS based journals for CO generated
	// VolumeName to backing RBD images
	volJournal  *journal.Config
	snapJournal *journal.Config
	// rbdHardMaxCloneDepth is the hard limit for maximum number of nested volume clones that are taken before a flatten occurs
	rbdHardMaxCloneDepth uint

	// rbdSoftMaxCloneDepth is the soft limit for maximum number of nested volume clones that are taken before a flatten occurs
	rbdSoftMaxCloneDepth uint
	maxSnapshotsOnImage  uint
	skipForceFlatten     bool
)

// NewDriver returns new rbd driver.
func NewDriver() *Driver {
	return &Driver{}
}

// NewIdentityServer initialize a identity server for rbd CSI driver.
func NewIdentityServer(d *csicommon.CSIDriver) *IdentityServer {
	return &IdentityServer{
		DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
	}
}

// NewControllerServer initialize a controller server for rbd CSI driver.
func NewControllerServer(d *csicommon.CSIDriver) *ControllerServer {
	return &ControllerServer{
		DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
		VolumeLocks:             util.NewVolumeLocks(),
		SnapshotLocks:           util.NewVolumeLocks(),
		OperationLocks:          util.NewOperationLock(),
	}
}

// NewNodeServer initialize a node server for rbd CSI driver.
func NewNodeServer(d *csicommon.CSIDriver, t string, topology map[string]string) (*NodeServer, error) {
	mounter := mount.New("")
	return &NodeServer{
		DefaultNodeServer: csicommon.NewDefaultNodeServer(d, t, topology),
		mounter:           mounter,
		VolumeLocks:       util.NewVolumeLocks(),
	}, nil
}

// Run start a non-blocking grpc controller,node and identityserver for
// rbd CSI driver which can serve multiple parallel requests.
func (r *Driver) Run(conf *util.Config) {
	var err error
	var topology map[string]string

	// Create ceph.conf for use with CLI commands
	if err = util.WriteCephConfig(); err != nil {
		util.FatalLogMsg("failed to write ceph configuration file (%v)", err)
	}

	// Use passed in instance ID, if provided for omap suffix naming
	if conf.InstanceID != "" {
		CSIInstanceID = conf.InstanceID
	}

	// update clone soft and hard limit
	rbdHardMaxCloneDepth = conf.RbdHardMaxCloneDepth
	rbdSoftMaxCloneDepth = conf.RbdSoftMaxCloneDepth
	skipForceFlatten = conf.SkipForceFlatten
	maxSnapshotsOnImage = conf.MaxSnapshotsOnImage
	// Create instances of the volume and snapshot journal
	volJournal = journal.NewCSIVolumeJournal(CSIInstanceID)
	snapJournal = journal.NewCSISnapshotJournal(CSIInstanceID)

	// Initialize default library driver
	r.cd = csicommon.NewCSIDriver(conf.DriverName, util.DriverVersion, conf.NodeID)
	if r.cd == nil {
		util.FatalLogMsg("Failed to initialize CSI Driver.")
	}
	if conf.IsControllerServer || !conf.IsNodeServer {
		r.cd.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
			csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
			csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
		})
		// We only support the multi-writer option when using block, but it's a supported capability for the plugin in general
		// In addition, we want to add the remaining modes like MULTI_NODE_READER_ONLY,
		// MULTI_NODE_SINGLE_WRITER etc, but need to do some verification of RO modes first
		// will work those as follow up features
		r.cd.AddVolumeCapabilityAccessModes(
			[]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
				csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER})
	}

	// Create GRPC servers
	r.ids = NewIdentityServer(r.cd)

	if conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			util.FatalLogMsg(err.Error())
		}
		r.ns, err = NewNodeServer(r.cd, conf.Vtype, topology)
		if err != nil {
			util.FatalLogMsg("failed to start node server, err %v\n", err)
		}
	}

	if conf.IsControllerServer {
		r.cs = NewControllerServer(r.cd)
	}
	if !conf.IsControllerServer && !conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			util.FatalLogMsg(err.Error())
		}
		r.ns, err = NewNodeServer(r.cd, conf.Vtype, topology)
		if err != nil {
			util.FatalLogMsg("failed to start node server, err %v\n", err)
		}
		r.cs = NewControllerServer(r.cd)
	}

	s := csicommon.NewNonBlockingGRPCServer()
	s.Start(conf.Endpoint, conf.HistogramOption, r.ids, r.cs, r.ns, conf.EnableGRPCMetrics)
	if conf.EnableGRPCMetrics {
		util.WarningLogMsg("EnableGRPCMetrics is deprecated")
		go util.StartMetricsServer(conf)
	}
	s.Wait()
}
<|endoftext|>


<commit_before>package trafo

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/101loops/hrd/entity"
	"github.com/101loops/hrd/internal/types"
	"github.com/101loops/iszero"

	ae "appengine"
	ds "appengine/datastore"
)

var (
	nowFunc = func() time.Time {
		return time.Now()
	}
)

// Save saves the entity to datastore properties.
func (doc *Doc) Save(ctx ae.Context) (props []*ds.Property, err error) {
	src := doc.get()

	// event hook: before save
	if hook, ok := src.(entity.BeforeSaver); ok {
		if err = hook.BeforeSave(); err != nil {
			return
		}
	}

	// timestamp
	now := nowFunc()
	if ts, ok := src.(entity.CreateTimestamper); ok {
		ts.SetCreatedAt(now)
	}
	if ts, ok := src.(entity.UpdateTimestamper); ok {
		ts.SetUpdatedAt(now)
	}

	// export properties
	props, err = doc.toProperties(ctx, "", []string{""}, false)
	if err != nil {
		return
	}

	// event hook: after save
	if hook, ok := src.(entity.AfterSaver); ok {
		if err = hook.AfterSave(); err != nil {
			return
		}
	}

	return
}

func (doc *Doc) toProperties(ctx ae.Context, prefix string, tags []string, multi bool) (res []*ds.Property, err error) {
	var props []*ds.Property
	res = make([]*ds.Property, 0)

	srcVal := doc.val()
	for _, fCodec := range doc.codec.Fields {
		fVal := srcVal.Field(fCodec.Index)
		if !fVal.IsValid() || !fVal.CanSet() {
			continue
		}

		name := fCodec.Attrs["label"].(string)
		if name == "-" {
			continue
		}
		aggrTags := append(tags, fCodec.Tag.Modifiers()...)

		// for slice fields (that aren't []byte), save each element
		if fVal.Kind() == reflect.Slice && fVal.Type() != typeOfByteSlice {
			for i := 0; i < fVal.Len(); i++ {
				props, err = fieldToProps(ctx, prefix, name, aggrTags, true, fVal.Index(i))
				if err != nil {
					return
				}
				res = append(res, props...)
			}
			continue
		}

		props, err = fieldToProps(ctx, prefix, name, aggrTags, multi, fVal)
		if err != nil {
			return
		}
		res = append(res, props...)
	}

	return
}

func fieldToProps(ctx ae.Context, prefix, name string, tags []string, multi bool, v reflect.Value) (props []*ds.Property, err error) {

	// process tags
	indexed := false
	for _, tag := range tags {
		tag = strings.ToLower(tag)
		if tag == "omitempty" {
			if iszero.Value(v) {
				return // ignore complete field if empty
			}
		} else if strings.HasPrefix(tag, "index") {
			indexed = true
			if strings.HasSuffix(tag, ":omitempty") && iszero.Value(v) {
				indexed = false // ignore index if empty
			}
		} else if tag != "" {
			err = fmt.Errorf("unknown tag %q", tag)
			return
		}
	}

	p := &ds.Property{Name: name, NoIndex: !indexed, Multiple: multi}
	if prefix != "" {
		p.Name = prefix + propertySeparator + p.Name
	}
	props = []*ds.Property{p}

	// serialize
	switch x := v.Interface().(type) {
	case *ds.Key:
		p.Value = x
	case types.DSKeyConverter:
		p.Value = x.ToDSKey(ctx)
	case time.Time:
		p.Value = x
	case ae.BlobKey:
		p.Value = x
	case ae.GeoPoint:
		p.Value = x
	case []byte:
		p.Value = x
		p.NoIndex = true
	default:
		switch v.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			p.Value = v.Int()
		case reflect.Bool:
			p.Value = v.Bool()
		case reflect.String:
			p.Value = v.String()
		case reflect.Float32, reflect.Float64:
			p.Value = v.Float()
		case reflect.Struct:
			if !v.CanAddr() {
				return nil, fmt.Errorf("unsupported property %q (unaddressable)", name)
			}

			sub, err := newDocFromInst(v.Addr().Interface())
			if err != nil {
				return nil, fmt.Errorf("unsupported property %q (%v)", name, err)
			}

			return sub.toProperties(ctx, name, tags, multi)
		}
	}

	if p.Value == nil {
		err = fmt.Errorf("unsupported struct field type %q", v.Type())
	}

	return
}
<commit_msg>simplify code<commit_after>package trafo

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/101loops/hrd/entity"
	"github.com/101loops/hrd/internal/types"
	"github.com/101loops/iszero"

	ae "appengine"
	ds "appengine/datastore"
)

var (
	nowFunc = func() time.Time {
		return time.Now()
	}
)

// Save saves the entity to datastore properties.
func (doc *Doc) Save(ctx ae.Context) (props []*ds.Property, err error) {
	src := doc.get()

	// event hook: before save
	if hook, ok := src.(entity.BeforeSaver); ok {
		if err = hook.BeforeSave(); err != nil {
			return
		}
	}

	// timestamp
	now := nowFunc()
	if ts, ok := src.(entity.CreateTimestamper); ok {
		ts.SetCreatedAt(now)
	}
	if ts, ok := src.(entity.UpdateTimestamper); ok {
		ts.SetUpdatedAt(now)
	}

	// export properties
	props, err = doc.toProperties(ctx, "", []string{""}, false)
	if err != nil {
		return
	}

	// event hook: after save
	if hook, ok := src.(entity.AfterSaver); ok {
		if err = hook.AfterSave(); err != nil {
			return
		}
	}

	return
}

func (doc *Doc) toProperties(ctx ae.Context, prefix string, tags []string, multi bool) (res []*ds.Property, err error) {
	var props []*ds.Property
	res = make([]*ds.Property, 0)

	srcVal := doc.val()
	for _, fCodec := range doc.codec.Fields {
		fVal := srcVal.Field(fCodec.Index)
		if !fVal.IsValid() || !fVal.CanSet() {
			continue
		}

		name := fCodec.Attrs["label"].(string)
		if name == "-" {
			continue
		}
		aggrTags := append(tags, fCodec.Tag.Modifiers()...)

		// for slice fields (that aren't []byte), save each element
		if fVal.Kind() == reflect.Slice && fVal.Type() != typeOfByteSlice {
			for i := 0; i < fVal.Len(); i++ {
				props, err = fieldToProps(ctx, prefix, name, aggrTags, true, fVal.Index(i))
				if err != nil {
					return
				}
				res = append(res, props...)
			}
			continue
		}

		props, err = fieldToProps(ctx, prefix, name, aggrTags, multi, fVal)
		if err != nil {
			return
		}
		res = append(res, props...)
	}

	return
}

func fieldToProps(ctx ae.Context, prefix, name string, tags []string, multi bool, v reflect.Value) (props []*ds.Property, err error) {

	// process tags
	indexed := false
	for _, tag := range tags {
		tag = strings.ToLower(tag)
		if tag == "omitempty" {
			if iszero.Value(v) {
				return // ignore complete field if empty
			}
		} else if strings.HasPrefix(tag, "index") {
			indexed = true
			if strings.HasSuffix(tag, ":omitempty") && iszero.Value(v) {
				indexed = false // ignore index if empty
			}
		} else if tag != "" {
			err = fmt.Errorf("unknown tag %q", tag)
			return
		}
	}

	p := &ds.Property{Name: name, NoIndex: !indexed, Multiple: multi}
	if prefix != "" {
		p.Name = prefix + propertySeparator + p.Name
	}
	props = []*ds.Property{p}

	// serialize
	switch x := v.Interface().(type) {
	case time.Time, ae.BlobKey, ae.GeoPoint, *ds.Key:
		p.Value = x
	case types.DSKeyConverter:
		p.Value = x.ToDSKey(ctx)
	case []byte:
		p.Value = x
		p.NoIndex = true
	default:
		switch v.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			p.Value = v.Int()
		case reflect.Bool:
			p.Value = v.Bool()
		case reflect.String:
			p.Value = v.String()
		case reflect.Float32, reflect.Float64:
			p.Value = v.Float()
		case reflect.Struct:
			if !v.CanAddr() {
				return nil, fmt.Errorf("unsupported property %q (unaddressable)", name)
			}

			sub, err := newDocFromInst(v.Addr().Interface())
			if err != nil {
				return nil, fmt.Errorf("unsupported property %q (%v)", name, err)
			}

			return sub.toProperties(ctx, name, tags, multi)
		}
	}

	if p.Value == nil {
		err = fmt.Errorf("unsupported struct field type %q", v.Type())
	}

	return
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved.

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 nfc

import (
	"context"
	"errors"
	"fmt"
	"io"
	"path"

	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/progress"
	"github.com/vmware/govmomi/vim25/soap"
	"github.com/vmware/govmomi/vim25/types"
)

type Lease struct {
	types.ManagedObjectReference

	c *vim25.Client
}

func NewLease(c *vim25.Client, ref types.ManagedObjectReference) *Lease {
	return &Lease{ref, c}
}

// Abort wraps methods.Abort
func (l *Lease) Abort(ctx context.Context, fault *types.LocalizedMethodFault) error {
	req := types.HttpNfcLeaseAbort{
		This:  l.Reference(),
		Fault: fault,
	}

	_, err := methods.HttpNfcLeaseAbort(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// Complete wraps methods.Complete
func (l *Lease) Complete(ctx context.Context) error {
	req := types.HttpNfcLeaseComplete{
		This: l.Reference(),
	}

	_, err := methods.HttpNfcLeaseComplete(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// GetManifest wraps methods.GetManifest
func (l *Lease) GetManifest(ctx context.Context) error {
	req := types.HttpNfcLeaseGetManifest{
		This: l.Reference(),
	}

	_, err := methods.HttpNfcLeaseGetManifest(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// Progress wraps methods.Progress
func (l *Lease) Progress(ctx context.Context, percent int32) error {
	req := types.HttpNfcLeaseProgress{
		This:    l.Reference(),
		Percent: percent,
	}

	_, err := methods.HttpNfcLeaseProgress(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

type LeaseInfo struct {
	types.HttpNfcLeaseInfo

	Items []FileItem
}

func (l *Lease) newLeaseInfo(li *types.HttpNfcLeaseInfo, items []types.OvfFileItem) (*LeaseInfo, error) {
	info := &LeaseInfo{
		HttpNfcLeaseInfo: *li,
	}

	for _, device := range li.DeviceUrl {
		u, err := l.c.ParseURL(device.Url)
		if err != nil {
			return nil, err
		}

		if device.SslThumbprint != "" {
			// TODO: prefer host management IP
			l.c.SetThumbprint(u.Host, device.SslThumbprint)
		}

		if len(items) == 0 {
			// this is an export
			item := types.OvfFileItem{
				DeviceId: device.Key,
				Path:     device.TargetId,
				Size:     device.FileSize,
			}

			if item.Size == 0 {
				item.Size = li.TotalDiskCapacityInKB * 1024
			}

			if item.Path == "" {
				item.Path = path.Base(device.Url)
			}

			info.Items = append(info.Items, NewFileItem(u, item))

			continue
		}

		// this is an import
		for _, item := range items {
			if device.ImportKey == item.DeviceId {
				info.Items = append(info.Items, NewFileItem(u, item))
				break
			}
		}
	}

	return info, nil
}

func (l *Lease) Wait(ctx context.Context, items []types.OvfFileItem) (*LeaseInfo, error) {
	var lease mo.HttpNfcLease

	pc := property.DefaultCollector(l.c)
	err := property.Wait(ctx, pc, l.Reference(), []string{"state", "info", "error"}, func(pc []types.PropertyChange) bool {
		done := false

		for _, c := range pc {
			if c.Val == nil {
				continue
			}

			switch c.Name {
			case "error":
				val := c.Val.(types.LocalizedMethodFault)
				lease.Error = &val
				done = true
			case "info":
				val := c.Val.(types.HttpNfcLeaseInfo)
				lease.Info = &val
			case "state":
				lease.State = c.Val.(types.HttpNfcLeaseState)
				if lease.State != types.HttpNfcLeaseStateInitializing {
					done = true
				}
			}
		}

		return done
	})

	if err != nil {
		return nil, err
	}

	if lease.State == types.HttpNfcLeaseStateReady {
		return l.newLeaseInfo(lease.Info, items)
	}

	if lease.Error != nil {
		return nil, errors.New(lease.Error.LocalizedMessage)
	}

	return nil, fmt.Errorf("unexpected nfc lease state: %s", lease.State)
}

func (l *Lease) StartUpdater(ctx context.Context, info *LeaseInfo) *LeaseUpdater {
	return newLeaseUpdater(ctx, l, info)
}

func (l *Lease) Upload(ctx context.Context, item FileItem, f io.Reader, opts soap.Upload) error {
	if opts.Progress == nil {
		opts.Progress = item
	} else {
		opts.Progress = progress.Tee(item, opts.Progress)
	}

	// Non-disk files (such as .iso) use the PUT method.
	// Overwrite: t header is also required in this case (ovftool does the same)
	if item.Create {
		opts.Method = "PUT"
		opts.Headers = map[string]string{
			"Overwrite": "t",
		}
	} else {
		opts.Method = "POST"
		opts.Type = "application/x-vnd.vmware-streamVmdk"
	}

	return l.c.Upload(ctx, f, item.URL, &opts)
}

func (l *Lease) DownloadFile(ctx context.Context, file string, item FileItem, opts soap.Download) error {
	if opts.Progress == nil {
		opts.Progress = item
	} else {
		opts.Progress = progress.Tee(item, opts.Progress)
	}

	return l.c.DownloadFile(ctx, file, item.URL, &opts)
}
<commit_msg>Remove nested progress.Tee usage<commit_after>/*
Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved.

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 nfc

import (
	"context"
	"errors"
	"fmt"
	"io"
	"path"

	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/soap"
	"github.com/vmware/govmomi/vim25/types"
)

type Lease struct {
	types.ManagedObjectReference

	c *vim25.Client
}

func NewLease(c *vim25.Client, ref types.ManagedObjectReference) *Lease {
	return &Lease{ref, c}
}

// Abort wraps methods.Abort
func (l *Lease) Abort(ctx context.Context, fault *types.LocalizedMethodFault) error {
	req := types.HttpNfcLeaseAbort{
		This:  l.Reference(),
		Fault: fault,
	}

	_, err := methods.HttpNfcLeaseAbort(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// Complete wraps methods.Complete
func (l *Lease) Complete(ctx context.Context) error {
	req := types.HttpNfcLeaseComplete{
		This: l.Reference(),
	}

	_, err := methods.HttpNfcLeaseComplete(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// GetManifest wraps methods.GetManifest
func (l *Lease) GetManifest(ctx context.Context) error {
	req := types.HttpNfcLeaseGetManifest{
		This: l.Reference(),
	}

	_, err := methods.HttpNfcLeaseGetManifest(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

// Progress wraps methods.Progress
func (l *Lease) Progress(ctx context.Context, percent int32) error {
	req := types.HttpNfcLeaseProgress{
		This:    l.Reference(),
		Percent: percent,
	}

	_, err := methods.HttpNfcLeaseProgress(ctx, l.c, &req)
	if err != nil {
		return err
	}

	return nil
}

type LeaseInfo struct {
	types.HttpNfcLeaseInfo

	Items []FileItem
}

func (l *Lease) newLeaseInfo(li *types.HttpNfcLeaseInfo, items []types.OvfFileItem) (*LeaseInfo, error) {
	info := &LeaseInfo{
		HttpNfcLeaseInfo: *li,
	}

	for _, device := range li.DeviceUrl {
		u, err := l.c.ParseURL(device.Url)
		if err != nil {
			return nil, err
		}

		if device.SslThumbprint != "" {
			// TODO: prefer host management IP
			l.c.SetThumbprint(u.Host, device.SslThumbprint)
		}

		if len(items) == 0 {
			// this is an export
			item := types.OvfFileItem{
				DeviceId: device.Key,
				Path:     device.TargetId,
				Size:     device.FileSize,
			}

			if item.Size == 0 {
				item.Size = li.TotalDiskCapacityInKB * 1024
			}

			if item.Path == "" {
				item.Path = path.Base(device.Url)
			}

			info.Items = append(info.Items, NewFileItem(u, item))

			continue
		}

		// this is an import
		for _, item := range items {
			if device.ImportKey == item.DeviceId {
				info.Items = append(info.Items, NewFileItem(u, item))
				break
			}
		}
	}

	return info, nil
}

func (l *Lease) Wait(ctx context.Context, items []types.OvfFileItem) (*LeaseInfo, error) {
	var lease mo.HttpNfcLease

	pc := property.DefaultCollector(l.c)
	err := property.Wait(ctx, pc, l.Reference(), []string{"state", "info", "error"}, func(pc []types.PropertyChange) bool {
		done := false

		for _, c := range pc {
			if c.Val == nil {
				continue
			}

			switch c.Name {
			case "error":
				val := c.Val.(types.LocalizedMethodFault)
				lease.Error = &val
				done = true
			case "info":
				val := c.Val.(types.HttpNfcLeaseInfo)
				lease.Info = &val
			case "state":
				lease.State = c.Val.(types.HttpNfcLeaseState)
				if lease.State != types.HttpNfcLeaseStateInitializing {
					done = true
				}
			}
		}

		return done
	})

	if err != nil {
		return nil, err
	}

	if lease.State == types.HttpNfcLeaseStateReady {
		return l.newLeaseInfo(lease.Info, items)
	}

	if lease.Error != nil {
		return nil, errors.New(lease.Error.LocalizedMessage)
	}

	return nil, fmt.Errorf("unexpected nfc lease state: %s", lease.State)
}

func (l *Lease) StartUpdater(ctx context.Context, info *LeaseInfo) *LeaseUpdater {
	return newLeaseUpdater(ctx, l, info)
}

func (l *Lease) Upload(ctx context.Context, item FileItem, f io.Reader, opts soap.Upload) error {
	if opts.Progress == nil {
		opts.Progress = item
	}

	// Non-disk files (such as .iso) use the PUT method.
	// Overwrite: t header is also required in this case (ovftool does the same)
	if item.Create {
		opts.Method = "PUT"
		opts.Headers = map[string]string{
			"Overwrite": "t",
		}
	} else {
		opts.Method = "POST"
		opts.Type = "application/x-vnd.vmware-streamVmdk"
	}

	return l.c.Upload(ctx, f, item.URL, &opts)
}

func (l *Lease) DownloadFile(ctx context.Context, file string, item FileItem, opts soap.Download) error {
	if opts.Progress == nil {
		opts.Progress = item
	}

	return l.c.DownloadFile(ctx, file, item.URL, &opts)
}
<|endoftext|>


<commit_before>// (c) 2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package health

import (
	"time"

	"github.com/ava-labs/avalanchego/utils/rpc"
)

// Client for Avalanche Health API Endpoint
type Client struct {
	requester rpc.EndpointRequester
}

// NewClient returns a client to interact with Health API endpoint
func NewClient(uri string, requestTimeout time.Duration) *Client {
	return &Client{
		requester: rpc.NewEndpointRequester(uri, "/ext/health", "health", requestTimeout),
	}
}

// GetLiveness returns a health check on the Avalanche node
func (c *Client) GetLiveness() (*APIHealthReply, error) {
	res := &APIHealthReply{}
	err := c.requester.SendRequest("getLiveness", struct{}{}, res)
	return res, err
}

// Health returns a health check on the Avalanche node
func (c *Client) Health() (*APIHealthReply, error) {
	res := &APIHealthReply{}
	err := c.requester.SendRequest("health", struct{}{}, res)
	return res, err
}

// AwaitHealthy queries the GetLiveness endpoint [checks] times, with a pause of [interval]
// in between checks and returns early if GetLiveness returns healthy
func (c *Client) AwaitHealthy(checks int, interval time.Duration) (bool, error) {
	var err error
	for i := 0; i < checks; i++ {
		var res *APIHealthReply
		res, err = c.GetLiveness()
		if err == nil && res.Healthy {
			return true, nil
		}
		time.Sleep(interval)
	}
	return false, err
}
<commit_msg>removed deprecated call from the health client<commit_after>// (c) 2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package health

import (
	"errors"
	"time"

	"github.com/ava-labs/avalanchego/utils/rpc"
)

var errInvalidNumberOfChecks = errors.New("expected at least 1 check attempt")

// Client for Avalanche Health API Endpoint
type Client struct {
	requester rpc.EndpointRequester
}

// NewClient returns a client to interact with Health API endpoint
func NewClient(uri string, requestTimeout time.Duration) *Client {
	return &Client{
		requester: rpc.NewEndpointRequester(uri, "/ext/health", "health", requestTimeout),
	}
}

// Health returns a health check on the Avalanche node
func (c *Client) Health() (*APIHealthReply, error) {
	res := &APIHealthReply{}
	err := c.requester.SendRequest("health", struct{}{}, res)
	return res, err
}

// AwaitHealthy queries the Health endpoint [checks] times, with a pause of
// [interval] in between checks and returns early if Health returns healthy
func (c *Client) AwaitHealthy(checks int, interval time.Duration) (bool, error) {
	if checks < 1 {
		return false, errInvalidNumberOfChecks
	}

	// Check health once outside the loop to avoid sleeping unnecessarily.
	res, err := c.Health()
	if err == nil && res.Healthy {
		return true, nil
	}

	for i := 1; i < checks; i++ {
		time.Sleep(interval)

		res, err = c.Health()
		if err == nil && res.Healthy {
			return true, nil
		}
	}
	return false, err
}
<|endoftext|>


<commit_before>// offers api
// https://github.com/topfreegames/offers
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2017 Top Free Games <backend@tfgco.com>

package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/sirupsen/logrus"
	e "github.com/topfreegames/offers/errors"
	"github.com/topfreegames/offers/models"
)

//OfferRequestHandler handler
type OfferRequestHandler struct {
	App    *App
	Method string
}

//ServeHTTP method
func (h *OfferRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch h.Method {
	case "get-offers":
		h.getOffers(w, r)
	case "claim":
		h.claimOffer(w, r)
	case "impressions":
		h.viewOffer(w, r)
	case "offer-info":
		h.offerInfo(w, r)
	}
}

func (h *OfferRequestHandler) getOffers(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	playerID := r.URL.Query().Get("player-id")
	gameID := r.URL.Query().Get("game-id")
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "getOffers",
		"gameID":    gameID,
		"playerID":  playerID,
	})

	if playerID == "" {
		err := fmt.Errorf("The player-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if gameID == "" {
		err := fmt.Errorf("The game-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	}
	currentTime := h.App.Clock.GetTime()
	filterAttrsList := r.URL.Query()
	filterAttrs := make(map[string]string)
	delete(filterAttrsList, "player-id")
	delete(filterAttrsList, "game-id")
	for k, v := range filterAttrsList {
		if len(v) == 0 || len(v) > 1 {
			err := fmt.Errorf("Filter attribute passed with invalid number of arguments. Key: %s", k)
			logger.WithError(err).Error("Failed to retrieve offer for player.")
			h.App.HandleError(w, http.StatusBadRequest, "A filter parameter is invalid.", err)
			return
		}
		filterAttrs[k] = v[0]
	}

	maxAge := h.App.MaxAge
	allowInefficientQueries := false
	var game *models.Game
	var err error
	err = mr.WithSegment(models.SegmentModel, func() error {
		game, err = models.GetGameByID(r.Context(), h.App.DB, gameID, mr)
		return err
	})
	if err != nil {
		logger.WithError(err).Error("Failed to retrieve game.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve game", err)
		return
	}
	metadata, err := game.GetMetadata()
	if err != nil {
		logger.WithError(err).Error("Failed to get game metadata.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to get game metadata", err)
		return
	}
	if val, ok := metadata["allowInefficientQueries"]; ok {
		if allowFromMeta, ok := val.(bool); ok {
			allowInefficientQueries = allowFromMeta
		}
	}
	if val, ok := metadata["cacheMaxAge"]; ok {
		if maxAgeFromMeta, ok := val.(float64); ok {
			maxAge = int64(maxAgeFromMeta)
		}
	}

	var offers map[string][]*models.OfferToReturn
	err = mr.WithSegment(models.SegmentModel, func() error {
		offers, err = models.GetAvailableOffers(r.Context(), h.App.DB, h.App.Cache, gameID, playerID, currentTime, h.App.OffersCacheMaxAge, filterAttrs, allowInefficientQueries, mr)
		return err
	})

	if err != nil {
		logger.WithError(err).Error("Failed to retrieve offers for player.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve offer for player", err)
		return
	}

	bytes, err := json.Marshal(offers)
	if err != nil {
		logger.WithError(err).Error("Failed to parse structs to JSON.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to parse structs to JSON", err)
		return
	}

	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", maxAge))

	logger.Info("Retrieved player offers successfully")
	WriteBytes(w, http.StatusOK, bytes)
}

func (h *OfferRequestHandler) claimOffer(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	payload := claimOfferPayloadFromCtx(r.Context())
	currentTime := h.App.Clock.GetTime()
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "claimOffer",
		"payload":   payload,
	})

	if payload.OfferInstanceID == "" && payload.ProductID == "" {
		validationError := e.NewValidationFailedError(errors.New("Id and ProductID cannot be both null"))
		h.App.HandleError(w, http.StatusUnprocessableEntity, validationError.Error(), validationError)
		return
	}

	contents, alreadyClaimed, nextAt, err := models.ClaimOffer(
		r.Context(),
		h.App.DB,
		payload.GameID,
		payload.OfferInstanceID,
		payload.PlayerID,
		payload.ProductID,
		payload.TransactionID,
		payload.Timestamp,
		currentTime,
		mr,
	)

	if err != nil {
		logger.WithError(err).Error("Failed to claim offer.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}

		h.App.HandleError(w, http.StatusInternalServerError, err.Error(), err)
		return
	}

	logger.WithField("alreadyClaimed", alreadyClaimed).Info("Claimed offer successfully")
	res := map[string]interface{}{
		"contents": contents,
	}
	if nextAt != 0 {
		res["nextAt"] = nextAt
	}
	bytesRes, _ := json.Marshal(res)
	if alreadyClaimed {
		WriteBytes(w, http.StatusConflict, bytesRes)
		return
	}

	WriteBytes(w, http.StatusOK, bytesRes)
}

func (h *OfferRequestHandler) viewOffer(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	payload := offerImpressionPayloadFromCtx(r.Context())
	offerInstanceID := paramKeyFromContext(r.Context())
	currentTime := h.App.Clock.GetTime()
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "viewOffer",
		"payload":   payload,
	})

	alreadyViewed, nextAt, err := models.ViewOffer(
		r.Context(),
		h.App.DB,
		payload.GameID,
		offerInstanceID,
		payload.PlayerID,
		payload.ImpressionID,
		currentTime,
		mr,
	)
	if err != nil {
		logger.WithError(err).Error("Failed to update offer impressions.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}

		h.App.HandleError(w, http.StatusInternalServerError, err.Error(), err)
		return
	}
	logger.Info("Updated offer impressions successfully")
	res := map[string]interface{}{}
	if nextAt != 0 {
		res["nextAt"] = nextAt
	}
	bytesRes, _ := json.Marshal(res)
	if alreadyViewed {
		WriteBytes(w, http.StatusConflict, bytesRes)
		return
	}
	WriteBytes(w, http.StatusOK, bytesRes)
}

func (h *OfferRequestHandler) offerInfo(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	playerID := r.URL.Query().Get("player-id")
	gameID := r.URL.Query().Get("game-id")
	offerInstanceID := r.URL.Query().Get("offer-id")
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":          "offerHandler",
		"operation":       "offerInfo",
		"gameID":          gameID,
		"playerID":        playerID,
		"offerInstanceID": offerInstanceID,
	})

	if playerID == "" {
		err := fmt.Errorf("The player-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if gameID == "" {
		err := fmt.Errorf("The game-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if offerInstanceID == "" {
		err := fmt.Errorf("The offer-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	}

	var err error
	var offer *models.OfferToReturn
	err = mr.WithSegment(models.SegmentModel, func() error {
		offer, err = models.GetOfferInfo(r.Context(), h.App.DB, gameID, offerInstanceID, h.App.OffersCacheMaxAge, mr)
		return err
	})

	if err != nil {
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			logger.WithError(err).Error("Failed to find offer for player.")
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve offer for player", err)
		return
	}

	bytes, err := json.Marshal(offer)
	if err != nil {
		logger.WithError(err).Error("Failed to parse structs to JSON.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to parse structs to JSON", err)
		return
	}

	maxAge := h.App.MaxAge
	var game *models.Game
	err = mr.WithSegment(models.SegmentModel, func() error {
		game, err = models.GetGameByID(r.Context(), h.App.DB, gameID, mr)
		return err
	})
	metadata, err := game.GetMetadata()
	if err == nil {
		if val, ok := metadata["cacheMaxAge"]; ok {
			if maxAgeFromMeta, ok := val.(float64); ok {
				maxAge = int64(maxAgeFromMeta)
			}
		}
	} else {
		logger.WithError(err).Warn("Failed to get game metadata.")
	}
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", maxAge))

	logger.Info("Retrieved player offer successfully")
	WriteBytes(w, http.StatusOK, bytes)
}
<commit_msg>Less verbose logging<commit_after>// offers api
// https://github.com/topfreegames/offers
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2017 Top Free Games <backend@tfgco.com>

package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/sirupsen/logrus"
	e "github.com/topfreegames/offers/errors"
	"github.com/topfreegames/offers/models"
)

//OfferRequestHandler handler
type OfferRequestHandler struct {
	App    *App
	Method string
}

//ServeHTTP method
func (h *OfferRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch h.Method {
	case "get-offers":
		h.getOffers(w, r)
	case "claim":
		h.claimOffer(w, r)
	case "impressions":
		h.viewOffer(w, r)
	case "offer-info":
		h.offerInfo(w, r)
	}
}

func (h *OfferRequestHandler) getOffers(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	playerID := r.URL.Query().Get("player-id")
	gameID := r.URL.Query().Get("game-id")
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "getOffers",
		"gameID":    gameID,
		"playerID":  playerID,
	})

	if playerID == "" {
		err := fmt.Errorf("The player-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if gameID == "" {
		err := fmt.Errorf("The game-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	}
	currentTime := h.App.Clock.GetTime()
	filterAttrsList := r.URL.Query()
	filterAttrs := make(map[string]string)
	delete(filterAttrsList, "player-id")
	delete(filterAttrsList, "game-id")
	for k, v := range filterAttrsList {
		if len(v) == 0 || len(v) > 1 {
			err := fmt.Errorf("Filter attribute passed with invalid number of arguments. Key: %s", k)
			logger.WithError(err).Error("Failed to retrieve offer for player.")
			h.App.HandleError(w, http.StatusBadRequest, "A filter parameter is invalid.", err)
			return
		}
		filterAttrs[k] = v[0]
	}

	maxAge := h.App.MaxAge
	allowInefficientQueries := false
	var game *models.Game
	var err error
	err = mr.WithSegment(models.SegmentModel, func() error {
		game, err = models.GetGameByID(r.Context(), h.App.DB, gameID, mr)
		return err
	})
	if err != nil {
		logger.WithError(err).Error("Failed to retrieve game.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve game", err)
		return
	}
	metadata, err := game.GetMetadata()
	if err != nil {
		logger.WithError(err).Error("Failed to get game metadata.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to get game metadata", err)
		return
	}
	if val, ok := metadata["allowInefficientQueries"]; ok {
		if allowFromMeta, ok := val.(bool); ok {
			allowInefficientQueries = allowFromMeta
		}
	}
	if val, ok := metadata["cacheMaxAge"]; ok {
		if maxAgeFromMeta, ok := val.(float64); ok {
			maxAge = int64(maxAgeFromMeta)
		}
	}

	var offers map[string][]*models.OfferToReturn
	err = mr.WithSegment(models.SegmentModel, func() error {
		offers, err = models.GetAvailableOffers(r.Context(), h.App.DB, h.App.Cache, gameID, playerID, currentTime, h.App.OffersCacheMaxAge, filterAttrs, allowInefficientQueries, mr)
		return err
	})

	if err != nil {
		logger.WithError(err).Error("Failed to retrieve offers for player.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve offer for player", err)
		return
	}

	bytes, err := json.Marshal(offers)
	if err != nil {
		logger.WithError(err).Error("Failed to parse structs to JSON.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to parse structs to JSON", err)
		return
	}

	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", maxAge))

	logger.Debug("Retrieved player offers successfully")
	WriteBytes(w, http.StatusOK, bytes)
}

func (h *OfferRequestHandler) claimOffer(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	payload := claimOfferPayloadFromCtx(r.Context())
	currentTime := h.App.Clock.GetTime()
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "claimOffer",
		"payload":   payload,
	})

	if payload.OfferInstanceID == "" && payload.ProductID == "" {
		validationError := e.NewValidationFailedError(errors.New("Id and ProductID cannot be both null"))
		h.App.HandleError(w, http.StatusUnprocessableEntity, validationError.Error(), validationError)
		return
	}

	contents, alreadyClaimed, nextAt, err := models.ClaimOffer(
		r.Context(),
		h.App.DB,
		payload.GameID,
		payload.OfferInstanceID,
		payload.PlayerID,
		payload.ProductID,
		payload.TransactionID,
		payload.Timestamp,
		currentTime,
		mr,
	)

	if err != nil {
		logger.WithError(err).Error("Failed to claim offer.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}

		h.App.HandleError(w, http.StatusInternalServerError, err.Error(), err)
		return
	}

	logger.WithField("alreadyClaimed", alreadyClaimed).Info("Claimed offer successfully")
	res := map[string]interface{}{
		"contents": contents,
	}
	if nextAt != 0 {
		res["nextAt"] = nextAt
	}
	bytesRes, _ := json.Marshal(res)
	if alreadyClaimed {
		WriteBytes(w, http.StatusConflict, bytesRes)
		return
	}

	WriteBytes(w, http.StatusOK, bytesRes)
}

func (h *OfferRequestHandler) viewOffer(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	payload := offerImpressionPayloadFromCtx(r.Context())
	offerInstanceID := paramKeyFromContext(r.Context())
	currentTime := h.App.Clock.GetTime()
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":    "offerHandler",
		"operation": "viewOffer",
		"payload":   payload,
	})

	alreadyViewed, nextAt, err := models.ViewOffer(
		r.Context(),
		h.App.DB,
		payload.GameID,
		offerInstanceID,
		payload.PlayerID,
		payload.ImpressionID,
		currentTime,
		mr,
	)
	if err != nil {
		logger.WithError(err).Error("Failed to update offer impressions.")
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}

		h.App.HandleError(w, http.StatusInternalServerError, err.Error(), err)
		return
	}
	logger.Debug("Updated offer impressions successfully")
	res := map[string]interface{}{}
	if nextAt != 0 {
		res["nextAt"] = nextAt
	}
	bytesRes, _ := json.Marshal(res)
	if alreadyViewed {
		WriteBytes(w, http.StatusConflict, bytesRes)
		return
	}
	WriteBytes(w, http.StatusOK, bytesRes)
}

func (h *OfferRequestHandler) offerInfo(w http.ResponseWriter, r *http.Request) {
	mr := metricsReporterFromCtx(r.Context())
	playerID := r.URL.Query().Get("player-id")
	gameID := r.URL.Query().Get("game-id")
	offerInstanceID := r.URL.Query().Get("offer-id")
	logger := h.App.Logger.WithFields(logrus.Fields{
		"source":          "offerHandler",
		"operation":       "offerInfo",
		"gameID":          gameID,
		"playerID":        playerID,
		"offerInstanceID": offerInstanceID,
	})

	if playerID == "" {
		err := fmt.Errorf("The player-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if gameID == "" {
		err := fmt.Errorf("The game-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	} else if offerInstanceID == "" {
		err := fmt.Errorf("The offer-id parameter cannot be empty")
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusBadRequest, err.Error(), err)
		return
	}

	var err error
	var offer *models.OfferToReturn
	err = mr.WithSegment(models.SegmentModel, func() error {
		offer, err = models.GetOfferInfo(r.Context(), h.App.DB, gameID, offerInstanceID, h.App.OffersCacheMaxAge, mr)
		return err
	})

	if err != nil {
		if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
			logger.WithError(err).Error("Failed to find offer for player.")
			h.App.HandleError(w, http.StatusNotFound, modelNotFound.Error(), modelNotFound)
			return
		}
		logger.WithError(err).Error("Failed to retrieve offer for player.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to retrieve offer for player", err)
		return
	}

	bytes, err := json.Marshal(offer)
	if err != nil {
		logger.WithError(err).Error("Failed to parse structs to JSON.")
		h.App.HandleError(w, http.StatusInternalServerError, "Failed to parse structs to JSON", err)
		return
	}

	maxAge := h.App.MaxAge
	var game *models.Game
	err = mr.WithSegment(models.SegmentModel, func() error {
		game, err = models.GetGameByID(r.Context(), h.App.DB, gameID, mr)
		return err
	})
	metadata, err := game.GetMetadata()
	if err == nil {
		if val, ok := metadata["cacheMaxAge"]; ok {
			if maxAgeFromMeta, ok := val.(float64); ok {
				maxAge = int64(maxAgeFromMeta)
			}
		}
	} else {
		logger.WithError(err).Warn("Failed to get game metadata.")
	}
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", maxAge))

	logger.Info("Retrieved player offer successfully")
	WriteBytes(w, http.StatusOK, bytes)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/gorilla/mux"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
)

func redirectLogin(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/login", 301)
}

type staticAssetPlugs struct {
	Origin string
}

type staticHtmlPlugs struct {
	CdnPrefix string
}

func initStaticRouter(router *mux.Router) error {
	asset := make(map[string]string)

	for _, dir := range []string{"js", "css", "images"} {
		files, err := ioutil.ReadDir("./" + dir)
		if err != nil {
			logger.Errorf("cannot read directory ./%s: %v", dir, err)
			return err
		}

		for _, file := range files {
			sl := string(os.PathSeparator)
			path := sl + dir + sl + file.Name()

			contents, err := ioutil.ReadFile("." + path)
			if err != nil {
				logger.Errorf("cannot read file %s: %v", path, err)
				return err
			}

			if dir == "js" {
				asset[path] = "window.API='" + os.Getenv("ORIGIN") + "/api';" + string(contents)
			} else {
				asset[path] = string(contents)
			}

			logger.Debugf("routing %s", path)
			router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
				fmt.Fprint(w, asset[r.URL.Path])
			})
		}
	}

	pages := []string{
		"login",
		"signup",
		"dashboard",
		"account",
	}

	html := make(map[string]string)
	for _, page := range pages {
		contents, err := ioutil.ReadFile(page + ".html")
		if err != nil {
			logger.Errorf("cannot read file %s.html: %v", page, err)
			return err
		}

		t, err := template.New(page).Delims("<<<", ">>>").Parse(string(contents))
		if err != nil {
			logger.Errorf("cannot parse /%s template: %v", page, err)
			return err
		}

		var buf bytes.Buffer
		t.Execute(&buf, &staticHtmlPlugs{CdnPrefix: os.Getenv("CDN_PREFIX")})

		html["/" + page] = buf.String()
	}

	for _, page := range pages {
		router.HandleFunc("/" + page, func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprint(w, html[r.URL.Path])
		})
	}

	router.HandleFunc("/", redirectLogin)

	return nil
}
<commit_msg>router_static.go: inject origin instead of api<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/gorilla/mux"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
)

func redirectLogin(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/login", 301)
}

type staticAssetPlugs struct {
	Origin string
}

type staticHtmlPlugs struct {
	CdnPrefix string
}

func initStaticRouter(router *mux.Router) error {
	asset := make(map[string]string)

	for _, dir := range []string{"js", "css", "images"} {
		files, err := ioutil.ReadDir("./" + dir)
		if err != nil {
			logger.Errorf("cannot read directory ./%s: %v", dir, err)
			return err
		}

		for _, file := range files {
			sl := string(os.PathSeparator)
			path := sl + dir + sl + file.Name()

			contents, err := ioutil.ReadFile("." + path)
			if err != nil {
				logger.Errorf("cannot read file %s: %v", path, err)
				return err
			}

			if dir == "js" {
				asset[path] = "window.origin='" + os.Getenv("ORIGIN") + "';" + string(contents)
			} else {
				asset[path] = string(contents)
			}

			logger.Debugf("routing %s", path)
			router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
				fmt.Fprint(w, asset[r.URL.Path])
			})
		}
	}

	pages := []string{
		"login",
		"signup",
		"dashboard",
		"account",
	}

	html := make(map[string]string)
	for _, page := range pages {
		contents, err := ioutil.ReadFile(page + ".html")
		if err != nil {
			logger.Errorf("cannot read file %s.html: %v", page, err)
			return err
		}

		t, err := template.New(page).Delims("<<<", ">>>").Parse(string(contents))
		if err != nil {
			logger.Errorf("cannot parse /%s template: %v", page, err)
			return err
		}

		var buf bytes.Buffer
		t.Execute(&buf, &staticHtmlPlugs{CdnPrefix: os.Getenv("CDN_PREFIX")})

		html["/" + page] = buf.String()
	}

	for _, page := range pages {
		router.HandleFunc("/" + page, func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprint(w, html[r.URL.Path])
		})
	}

	router.HandleFunc("/", redirectLogin)

	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/config"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	// VolumeDriver is the string returned in the handshake protocol.
	VolumeDriver = "VolumeDriver"
)

// Implementation of the Docker volumes plugin specification.
type driver struct {
	restBase
}

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
	Opts map[string]string
}

type volumeResponse struct {
	Err string
}

type volumePathResponse struct {
	Mountpoint string
	volumeResponse
}

type volumeInfo struct {
	Name       string
	Mountpoint string
}

func newVolumePlugin(name string) restServer {
	return &driver{restBase{name: name, version: "0.3"}}
}

func (d *driver) String() string {
	return d.name
}

func volDriverPath(method string) string {
	return fmt.Sprintf("/%s.%s", VolumeDriver, method)
}

func (d *driver) volNotFound(request string, id string, e error, w http.ResponseWriter) error {
	err := fmt.Errorf("Failed to locate volume: " + e.Error())
	d.logRequest(request, id).Warnln(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) volNotMounted(request string, id string) error {
	err := fmt.Errorf("volume not mounted")
	d.logRequest(request, id).Debugln(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) Routes() []*Route {
	return []*Route{
		&Route{verb: "POST", path: volDriverPath("Create"), fn: d.create},
		&Route{verb: "POST", path: volDriverPath("Remove"), fn: d.remove},
		&Route{verb: "POST", path: volDriverPath("Mount"), fn: d.mount},
		&Route{verb: "POST", path: volDriverPath("Path"), fn: d.path},
		&Route{verb: "POST", path: volDriverPath("List"), fn: d.list},
		&Route{verb: "POST", path: volDriverPath("Get"), fn: d.get},
		&Route{verb: "POST", path: volDriverPath("Unmount"), fn: d.unmount},
		&Route{verb: "POST", path: "/Plugin.Activate", fn: d.handshake},
		&Route{verb: "GET", path: "/status", fn: d.status},
	}
}

func (d *driver) emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) errorResponse(w http.ResponseWriter, err error) {
	json.NewEncoder(w).Encode(&volumeResponse{Err: err.Error()})
}

func (d *driver) volFromName(name string) (*api.Volume, error) {
	v, err := volume.Get(d.name)
	if err != nil {
		return nil, fmt.Errorf("Cannot locate volume driver for %s: %s", d.name, err.Error())
	}
	vols, err := v.Inspect([]string{name})
	if err == nil && len(vols) == 1 {
		return vols[0], nil
	}
	vols, err = v.Enumerate(&api.VolumeLocator{Name: name}, nil)
	if err == nil && len(vols) == 1 {
		return vols[0], nil
	}
	return nil, fmt.Errorf("Cannot locate volume %s", name)
}

func (d *driver) decode(method string, w http.ResponseWriter, r *http.Request) (*volumeRequest, error) {
	var request volumeRequest
	err := json.NewDecoder(r.Body).Decode(&request)
	if err != nil {
		e := fmt.Errorf("Unable to decode JSON payload")
		d.sendError(method, "", w, e.Error()+":"+err.Error(), http.StatusBadRequest)
		return nil, e
	}
	d.logRequest(method, request.Name).Debugln("")
	return &request, nil
}

func (d *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{VolumeDriver},
	})
	if err != nil {
		d.sendError("handshake", "", w, "encode error", http.StatusInternalServerError)
		return
	}
	d.logRequest("handshake", "").Debugln("Handshake completed")
}

func (d *driver) status(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, fmt.Sprintln("osd plugin", d.version))
}

func (d *driver) specFromOpts(Opts map[string]string) *api.VolumeSpec {
	var spec api.VolumeSpec
	spec.VolumeLabels = make(map[string]string)
	for k, v := range Opts {
		switch k {
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			sizeMulti := uint64(1024 * 1024 * 1024)
			if strings.HasSuffix(v, "G") || strings.HasSuffix(v, "g") {
				sizeMulti = 1024 * 1024 * 1024
				last := len(v) - 1
				v = v[:last]
			}

			size, _ := strconv.ParseUint(v, 10, 64)
			spec.Size = size * sizeMulti
		case api.SpecFilesystem:
			value, _ := api.FSTypeSimpleValueOf(v)
			spec.Format = value
		case api.SpecBlockSize:
			blockSize, _ := strconv.ParseInt(v, 10, 64)
			spec.BlockSize = blockSize
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HaLevel = haLevel
		case api.SpecCos:
			value, _ := strconv.ParseUint(v, 10, 32)
			spec.Cos = uint32(value)
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseUint(v, 10, 32)
			spec.SnapshotInterval = uint32(snapshotInterval)
		default:
			spec.VolumeLabels[k] = v
		}
	}
	return &spec
}

func (d *driver) create(w http.ResponseWriter, r *http.Request) {
	method := "create"
	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logRequest(method, request.Name).Infoln("")
	if _, err = d.volFromName(request.Name); err != nil {
		v, err := volume.Get(d.name)
		if err != nil {
			d.errorResponse(w, err)
			return
		}
		spec := d.specFromOpts(request.Opts)
		if _, err := v.Create(&api.VolumeLocator{Name: request.Name}, nil, spec); err != nil {
			d.errorResponse(w, err)
			return
		}
	}
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) remove(w http.ResponseWriter, r *http.Request) {
	method := "remove"
	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logRequest(method, request.Name).Infoln("")
	// It is an error if the volume doesn't exist.
	if _, err := d.volFromName(request.Name); err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) mount(w http.ResponseWriter, r *http.Request) {
	var response volumePathResponse
	method := "mount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver")
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	d.logRequest(method, request.Name).Debugln("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	// If this is a block driver, first attach the volume.
	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		attachPath, err := v.Attach(vol.Id)
		if err != nil {
			d.logRequest(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
			d.errorResponse(w, err)
			return
		}
		d.logRequest(method, request.Name).Debugf("response %v", attachPath)
	}

	// Now mount it.
	response.Mountpoint = path.Join(config.MountBase, request.Name)
	os.MkdirAll(response.Mountpoint, 0755)

	err = v.Mount(vol.Id, response.Mountpoint)
	if err != nil {
		d.logRequest(method, request.Name).Warnf("Cannot mount volume %v, %v",
			response.Mountpoint, err)
		d.errorResponse(w, err)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	os.MkdirAll(response.Mountpoint, 0755)

	d.logRequest(method, request.Name).Infof("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) path(w http.ResponseWriter, r *http.Request) {
	method := "path"
	var response volumePathResponse

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	d.logRequest(method, request.Name).Debugf("")
	response.Mountpoint = vol.AttachPath
	if response.Mountpoint == "" {
		e := d.volNotMounted(method, request.Name)
		d.errorResponse(w, e)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	d.logRequest(method, request.Name).Debugf("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) list(w http.ResponseWriter, r *http.Request) {
	method := "list"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	vols, err := v.Enumerate(nil, nil)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	volInfo := make([]volumeInfo, len(vols))
	for i, v := range vols {
		volInfo[i].Name = v.Locator.Name
		if v.AttachPath != "" {
			volInfo[i].Mountpoint = path.Join(v.AttachPath, config.DataDir)
		}
	}
	json.NewEncoder(w).Encode(map[string][]volumeInfo{"Volumes": volInfo})
}

func (d *driver) get(w http.ResponseWriter, r *http.Request) {
	method := "get"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	volInfo := volumeInfo{Name: request.Name}
	if vol.AttachPath != "" {
		volInfo.Mountpoint = path.Join(vol.AttachPath, config.DataDir)
	}

	json.NewEncoder(w).Encode(map[string]volumeInfo{"Volume": volInfo})
}

func (d *driver) unmount(w http.ResponseWriter, r *http.Request) {
	method := "unmount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logRequest(method, request.Name).Infoln("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	mountpoint := path.Join(config.MountBase, request.Name)
	err = v.Unmount(vol.Id, mountpoint)
	if err != nil {
		d.logRequest(method, request.Name).Warnf("Cannot unmount volume %v, %v",
			mountpoint, err)
		d.errorResponse(w, err)
		return
	}

	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		_ = v.Detach(vol.Id)
	}
	d.emptyResponse(w)
}
<commit_msg>default spec FS to ext4<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/config"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	// VolumeDriver is the string returned in the handshake protocol.
	VolumeDriver = "VolumeDriver"
)

// Implementation of the Docker volumes plugin specification.
type driver struct {
	restBase
}

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
	Opts map[string]string
}

type volumeResponse struct {
	Err string
}

type volumePathResponse struct {
	Mountpoint string
	volumeResponse
}

type volumeInfo struct {
	Name       string
	Mountpoint string
}

func newVolumePlugin(name string) restServer {
	return &driver{restBase{name: name, version: "0.3"}}
}

func (d *driver) String() string {
	return d.name
}

func volDriverPath(method string) string {
	return fmt.Sprintf("/%s.%s", VolumeDriver, method)
}

func (d *driver) volNotFound(request string, id string, e error, w http.ResponseWriter) error {
	err := fmt.Errorf("Failed to locate volume: " + e.Error())
	d.logRequest(request, id).Warnln(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) volNotMounted(request string, id string) error {
	err := fmt.Errorf("volume not mounted")
	d.logRequest(request, id).Debugln(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) Routes() []*Route {
	return []*Route{
		&Route{verb: "POST", path: volDriverPath("Create"), fn: d.create},
		&Route{verb: "POST", path: volDriverPath("Remove"), fn: d.remove},
		&Route{verb: "POST", path: volDriverPath("Mount"), fn: d.mount},
		&Route{verb: "POST", path: volDriverPath("Path"), fn: d.path},
		&Route{verb: "POST", path: volDriverPath("List"), fn: d.list},
		&Route{verb: "POST", path: volDriverPath("Get"), fn: d.get},
		&Route{verb: "POST", path: volDriverPath("Unmount"), fn: d.unmount},
		&Route{verb: "POST", path: "/Plugin.Activate", fn: d.handshake},
		&Route{verb: "GET", path: "/status", fn: d.status},
	}
}

func (d *driver) emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) errorResponse(w http.ResponseWriter, err error) {
	json.NewEncoder(w).Encode(&volumeResponse{Err: err.Error()})
}

func (d *driver) volFromName(name string) (*api.Volume, error) {
	v, err := volume.Get(d.name)
	if err != nil {
		return nil, fmt.Errorf("Cannot locate volume driver for %s: %s", d.name, err.Error())
	}
	vols, err := v.Inspect([]string{name})
	if err == nil && len(vols) == 1 {
		return vols[0], nil
	}
	vols, err = v.Enumerate(&api.VolumeLocator{Name: name}, nil)
	if err == nil && len(vols) == 1 {
		return vols[0], nil
	}
	return nil, fmt.Errorf("Cannot locate volume %s", name)
}

func (d *driver) decode(method string, w http.ResponseWriter, r *http.Request) (*volumeRequest, error) {
	var request volumeRequest
	err := json.NewDecoder(r.Body).Decode(&request)
	if err != nil {
		e := fmt.Errorf("Unable to decode JSON payload")
		d.sendError(method, "", w, e.Error()+":"+err.Error(), http.StatusBadRequest)
		return nil, e
	}
	d.logRequest(method, request.Name).Debugln("")
	return &request, nil
}

func (d *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{VolumeDriver},
	})
	if err != nil {
		d.sendError("handshake", "", w, "encode error", http.StatusInternalServerError)
		return
	}
	d.logRequest("handshake", "").Debugln("Handshake completed")
}

func (d *driver) status(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, fmt.Sprintln("osd plugin", d.version))
}

func (d *driver) specFromOpts(Opts map[string]string) *api.VolumeSpec {
	spec := api.VolumeSpec{
		VolumeLabels: make(map[string]string),
		Format:       api.FSType_FS_TYPE_EXT4,
	}

	for k, v := range Opts {
		switch k {
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			sizeMulti := uint64(1024 * 1024 * 1024)
			if strings.HasSuffix(v, "G") || strings.HasSuffix(v, "g") {
				sizeMulti = 1024 * 1024 * 1024
				last := len(v) - 1
				v = v[:last]
			}

			size, _ := strconv.ParseUint(v, 10, 64)
			spec.Size = size * sizeMulti
		case api.SpecFilesystem:
			value, _ := api.FSTypeSimpleValueOf(v)
			spec.Format = value
		case api.SpecBlockSize:
			blockSize, _ := strconv.ParseInt(v, 10, 64)
			spec.BlockSize = blockSize
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HaLevel = haLevel
		case api.SpecCos:
			value, _ := strconv.ParseUint(v, 10, 32)
			spec.Cos = uint32(value)
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseUint(v, 10, 32)
			spec.SnapshotInterval = uint32(snapshotInterval)
		default:
			spec.VolumeLabels[k] = v
		}
	}
	return &spec
}

func (d *driver) create(w http.ResponseWriter, r *http.Request) {
	method := "create"
	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logRequest(method, request.Name).Infoln("")
	if _, err = d.volFromName(request.Name); err != nil {
		v, err := volume.Get(d.name)
		if err != nil {
			d.errorResponse(w, err)
			return
		}
		spec := d.specFromOpts(request.Opts)
		if _, err := v.Create(&api.VolumeLocator{Name: request.Name}, nil, spec); err != nil {
			d.errorResponse(w, err)
			return
		}
	}
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) remove(w http.ResponseWriter, r *http.Request) {
	method := "remove"
	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logRequest(method, request.Name).Infoln("")
	// It is an error if the volume doesn't exist.
	if _, err := d.volFromName(request.Name); err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) mount(w http.ResponseWriter, r *http.Request) {
	var response volumePathResponse
	method := "mount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver")
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	d.logRequest(method, request.Name).Debugln("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	// If this is a block driver, first attach the volume.
	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		attachPath, err := v.Attach(vol.Id)
		if err != nil {
			d.logRequest(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
			d.errorResponse(w, err)
			return
		}
		d.logRequest(method, request.Name).Debugf("response %v", attachPath)
	}

	// Now mount it.
	response.Mountpoint = path.Join(config.MountBase, request.Name)
	os.MkdirAll(response.Mountpoint, 0755)

	err = v.Mount(vol.Id, response.Mountpoint)
	if err != nil {
		d.logRequest(method, request.Name).Warnf("Cannot mount volume %v, %v",
			response.Mountpoint, err)
		d.errorResponse(w, err)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	os.MkdirAll(response.Mountpoint, 0755)

	d.logRequest(method, request.Name).Infof("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) path(w http.ResponseWriter, r *http.Request) {
	method := "path"
	var response volumePathResponse

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	d.logRequest(method, request.Name).Debugf("")
	response.Mountpoint = vol.AttachPath
	if response.Mountpoint == "" {
		e := d.volNotMounted(method, request.Name)
		d.errorResponse(w, e)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	d.logRequest(method, request.Name).Debugf("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) list(w http.ResponseWriter, r *http.Request) {
	method := "list"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	vols, err := v.Enumerate(nil, nil)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	volInfo := make([]volumeInfo, len(vols))
	for i, v := range vols {
		volInfo[i].Name = v.Locator.Name
		if v.AttachPath != "" {
			volInfo[i].Mountpoint = path.Join(v.AttachPath, config.DataDir)
		}
	}
	json.NewEncoder(w).Encode(map[string][]volumeInfo{"Volumes": volInfo})
}

func (d *driver) get(w http.ResponseWriter, r *http.Request) {
	method := "get"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	volInfo := volumeInfo{Name: request.Name}
	if vol.AttachPath != "" {
		volInfo.Mountpoint = path.Join(vol.AttachPath, config.DataDir)
	}

	json.NewEncoder(w).Encode(map[string]volumeInfo{"Volume": volInfo})
}

func (d *driver) unmount(w http.ResponseWriter, r *http.Request) {
	method := "unmount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logRequest(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logRequest(method, request.Name).Infoln("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	mountpoint := path.Join(config.MountBase, request.Name)
	err = v.Unmount(vol.Id, mountpoint)
	if err != nil {
		d.logRequest(method, request.Name).Warnf("Cannot unmount volume %v, %v",
			mountpoint, err)
		d.errorResponse(w, err)
		return
	}

	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		_ = v.Detach(vol.Id)
	}
	d.emptyResponse(w)
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/config"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	// VolumeDriver is the string returned in the handshake protocol.
	VolumeDriver = "VolumeDriver"
)

// Implementation of the Docker volumes plugin specification.
type driver struct {
	restBase
}

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
	Opts map[string]string
}

type volumeResponse struct {
	Err error
}
type volumePathResponse struct {
	Mountpoint string
	Err        error
}

type volumeInfo struct {
	vol *api.Volume
}

func newVolumePlugin(name string) restServer {
	return &driver{restBase{name: name, version: "0.3"}}
}

func (d *driver) String() string {
	return d.name
}

func volDriverPath(method string) string {
	return fmt.Sprintf("/%s.%s", VolumeDriver, method)
}

func (d *driver) volNotFound(request string, id string, e error, w http.ResponseWriter) error {
	err := fmt.Errorf("Failed to locate volume: " + e.Error())
	d.logReq(request, id).Warn(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) volNotMounted(request string, id string) error {
	err := fmt.Errorf("volume not mounted")
	d.logReq(request, id).Debug(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) Routes() []*Route {
	return []*Route{
		&Route{verb: "POST", path: volDriverPath("Create"), fn: d.create},
		&Route{verb: "POST", path: volDriverPath("Remove"), fn: d.remove},
		&Route{verb: "POST", path: volDriverPath("Mount"), fn: d.mount},
		&Route{verb: "POST", path: volDriverPath("Path"), fn: d.path},
		&Route{verb: "POST", path: volDriverPath("Unmount"), fn: d.unmount},
		&Route{verb: "POST", path: "/Plugin.Activate", fn: d.handshake},
		&Route{verb: "GET", path: "/status", fn: d.status},
	}
}

func (d *driver) emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) volFromName(name string) (*volumeInfo, error) {
	v, err := volume.Get(d.name)
	if err != nil {
		return nil, fmt.Errorf("Cannot locate volume driver for %s: %s", d.name, err.Error())
	}
	vols, err := v.Inspect([]api.VolumeID{api.VolumeID(name)})
	if err == nil && len(vols) == 1 {
		return &volumeInfo{vol: &vols[0]}, nil
	}
	vols, err = v.Enumerate(api.VolumeLocator{Name: name}, nil)
	if err == nil && len(vols) == 1 {
		return &volumeInfo{vol: &vols[0]}, nil
	}
	return nil, fmt.Errorf("Cannot locate volume %s", name)
}

func (d *driver) decode(method string, w http.ResponseWriter, r *http.Request) (*volumeRequest, error) {
	var request volumeRequest
	err := json.NewDecoder(r.Body).Decode(&request)
	if err != nil {
		e := fmt.Errorf("Unable to decode JSON payload")
		d.sendError(method, "", w, e.Error()+":"+err.Error(), http.StatusBadRequest)
		return nil, e
	}
	d.logReq(method, request.Name).Debug("")
	return &request, nil
}

func (d *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{VolumeDriver},
	})
	if err != nil {
		d.sendError("handshake", "", w, "encode error", http.StatusInternalServerError)
		return
	}
	d.logReq("handshake", "").Debug("Handshake completed")
}

func (d *driver) status(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, fmt.Sprintln("osd plugin", d.version))
}

func (d *driver) specFromOpts(Opts map[string]string) *api.VolumeSpec {
	var spec api.VolumeSpec
	for k, v := range Opts {
		switch k {
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			spec.Size, _ = strconv.ParseUint(v, 10, 64)
		case api.SpecFilesystem:
			spec.Format = api.Filesystem(v)
		case api.SpecBlockSize:
			blockSize, _ := strconv.ParseInt(v, 10, 64)
			spec.BlockSize = int(blockSize)
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HALevel = int(haLevel)
		case api.SpecCos:
			cos, _ := strconv.ParseInt(v, 10, 64)
			spec.Cos = api.VolumeCos(cos)
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseInt(v, 10, 64)
			spec.SnapshotInterval = int(snapshotInterval)
		}
	}
	return &spec
}

func (d *driver) create(w http.ResponseWriter, r *http.Request) {
	var err error
	method := "create"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logReq(method, request.Name).Info("")

	_, err = d.volFromName(request.Name)
	if err != nil {
		v, err := volume.Get(d.name)
		if err != nil {
			json.NewEncoder(w).Encode(&volumeResponse{Err: err})
			return
		}
		spec := d.specFromOpts(request.Opts)
		_, err = v.Create(api.VolumeLocator{Name: request.Name}, nil, spec)
		if err != nil {
			json.NewEncoder(w).Encode(&volumeResponse{Err: err})
			return
		}
	}

	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) remove(w http.ResponseWriter, r *http.Request) {
	method := "remove"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logReq(method, request.Name).Info("")

	// It is an error if the volume doesn't exist.
	_, err = d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		json.NewEncoder(w).Encode(&volumeResponse{Err: e})
		return
	}

	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) mount(w http.ResponseWriter, r *http.Request) {
	var response volumePathResponse
	method := "mount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logReq(method, "").Warn("Cannot locate volume driver")
		json.NewEncoder(w).Encode(&volumePathResponse{Err: err})
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		json.NewEncoder(w).Encode(&volumePathResponse{Err: err})
		return
	}

	d.logReq(method, request.Name).Debug("")

	volInfo, err := d.volFromName(request.Name)
	if err != nil {
		json.NewEncoder(w).Encode(&volumePathResponse{Err: err})
		return
	}

	// If this is a block driver, first attach the volume.
	if v.Type()&api.Block != 0 {
		attachPath, err := v.Attach(volInfo.vol.ID)
		if err != nil {
			d.logReq(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
			json.NewEncoder(w).Encode(&volumePathResponse{Err: err})
			return
		}
		d.logReq(method, request.Name).Debugf("response %v", attachPath)
	}

	// Now mount it.
	response.Mountpoint = path.Join(config.MountBase, request.Name)
	os.MkdirAll(response.Mountpoint, 0755)

	err = v.Mount(volInfo.vol.ID, response.Mountpoint)
	if err != nil {
		d.logReq(method, request.Name).Warnf("Cannot mount volume %v, %v",
			response.Mountpoint, err)
		json.NewEncoder(w).Encode(&volumePathResponse{Err: err})
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	os.MkdirAll(response.Mountpoint, 0755)

	d.logReq(method, request.Name).Infof("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) path(w http.ResponseWriter, r *http.Request) {
	method := "path"
	var response volumePathResponse

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	volInfo, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		json.NewEncoder(w).Encode(&volumePathResponse{Err: e})
		return
	}

	d.logReq(method, request.Name).Debug("")
	response.Mountpoint = volInfo.vol.AttachPath
	if response.Mountpoint == "" {
		e := d.volNotMounted(method, request.Name)
		json.NewEncoder(w).Encode(&volumePathResponse{Err: e})
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	d.logReq(method, request.Name).Debugf("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) unmount(w http.ResponseWriter, r *http.Request) {
	method := "unmount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logReq(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		json.NewEncoder(w).Encode(&volumeResponse{Err: err})
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logReq(method, request.Name).Info("")

	volInfo, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		json.NewEncoder(w).Encode(&volumeResponse{Err: e})
		return
	}

	mountpoint := path.Join(config.MountBase, request.Name)
	err = v.Unmount(volInfo.vol.ID, mountpoint)
	if err != nil {
		d.logReq(method, request.Name).Warnf("Cannot unmount volume %v, %v",
			mountpoint, err)
		json.NewEncoder(w).Encode(&volumeResponse{Err: err})
		return
	}

	if v.Type()&api.Block != 0 {
		_ = v.Detach(volInfo.vol.ID)
	}
	d.emptyResponse(w)
}
<commit_msg>support VolumeDriver.List and VolumeDriver.Get commands<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/config"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	// VolumeDriver is the string returned in the handshake protocol.
	VolumeDriver = "VolumeDriver"
)

// Implementation of the Docker volumes plugin specification.
type driver struct {
	restBase
}

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
	Opts map[string]string
}

type volumeResponse struct {
	Err string
}

type volumePathResponse struct {
	Mountpoint string
	volumeResponse
}

type volumeInfo struct {
	Name       string
	Mountpoint string
}

func newVolumePlugin(name string) restServer {
	return &driver{restBase{name: name, version: "0.3"}}
}

func (d *driver) String() string {
	return d.name
}

func volDriverPath(method string) string {
	return fmt.Sprintf("/%s.%s", VolumeDriver, method)
}

func (d *driver) volNotFound(request string, id string, e error, w http.ResponseWriter) error {
	err := fmt.Errorf("Failed to locate volume: " + e.Error())
	d.logReq(request, id).Warn(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) volNotMounted(request string, id string) error {
	err := fmt.Errorf("volume not mounted")
	d.logReq(request, id).Debug(http.StatusNotFound, " ", err.Error())
	return err
}

func (d *driver) Routes() []*Route {
	return []*Route{
		&Route{verb: "POST", path: volDriverPath("Create"), fn: d.create},
		&Route{verb: "POST", path: volDriverPath("Remove"), fn: d.remove},
		&Route{verb: "POST", path: volDriverPath("Mount"), fn: d.mount},
		&Route{verb: "POST", path: volDriverPath("Path"), fn: d.path},
		&Route{verb: "POST", path: volDriverPath("List"), fn: d.list},
		&Route{verb: "POST", path: volDriverPath("Get"), fn: d.get},
		&Route{verb: "POST", path: volDriverPath("Unmount"), fn: d.unmount},
		&Route{verb: "POST", path: "/Plugin.Activate", fn: d.handshake},
		&Route{verb: "GET", path: "/status", fn: d.status},
	}
}

func (d *driver) emptyResponse(w http.ResponseWriter) {
	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) errorResponse(w http.ResponseWriter, err error) {
	json.NewEncoder(w).Encode(&volumeResponse{Err: err.Error()})
}

func (d *driver) volFromName(name string) (*api.Volume, error) {
	v, err := volume.Get(d.name)
	if err != nil {
		return nil, fmt.Errorf("Cannot locate volume driver for %s: %s", d.name, err.Error())
	}
	vols, err := v.Inspect([]api.VolumeID{api.VolumeID(name)})
	if err == nil && len(vols) == 1 {
		return &vols[0], nil
	}
	vols, err = v.Enumerate(api.VolumeLocator{Name: name}, nil)
	if err == nil && len(vols) == 1 {
		return &vols[0], nil
	}
	return nil, fmt.Errorf("Cannot locate volume %s", name)
}

func (d *driver) decode(method string, w http.ResponseWriter, r *http.Request) (*volumeRequest, error) {
	var request volumeRequest
	err := json.NewDecoder(r.Body).Decode(&request)
	if err != nil {
		e := fmt.Errorf("Unable to decode JSON payload")
		d.sendError(method, "", w, e.Error()+":"+err.Error(), http.StatusBadRequest)
		return nil, e
	}
	d.logReq(method, request.Name).Debug("")
	return &request, nil
}

func (d *driver) handshake(w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(&handshakeResp{
		[]string{VolumeDriver},
	})
	if err != nil {
		d.sendError("handshake", "", w, "encode error", http.StatusInternalServerError)
		return
	}
	d.logReq("handshake", "").Debug("Handshake completed")
}

func (d *driver) status(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, fmt.Sprintln("osd plugin", d.version))
}

func (d *driver) specFromOpts(Opts map[string]string) *api.VolumeSpec {
	var spec api.VolumeSpec
	for k, v := range Opts {
		switch k {
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			spec.Size, _ = strconv.ParseUint(v, 10, 64)
		case api.SpecFilesystem:
			spec.Format = api.Filesystem(v)
		case api.SpecBlockSize:
			blockSize, _ := strconv.ParseInt(v, 10, 64)
			spec.BlockSize = int(blockSize)
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HALevel = int(haLevel)
		case api.SpecCos:
			cos, _ := strconv.ParseInt(v, 10, 64)
			spec.Cos = api.VolumeCos(cos)
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseInt(v, 10, 64)
			spec.SnapshotInterval = int(snapshotInterval)
		}
	}
	return &spec
}

func (d *driver) create(w http.ResponseWriter, r *http.Request) {
	var err error
	method := "create"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	d.logReq(method, request.Name).Info("")

	_, err = d.volFromName(request.Name)
	if err != nil {
		v, err := volume.Get(d.name)
		if err != nil {
			d.errorResponse(w, err)
			return
		}
		spec := d.specFromOpts(request.Opts)
		_, err = v.Create(api.VolumeLocator{Name: request.Name}, nil, spec)
		if err != nil {
			d.errorResponse(w, err)
			return
		}
	}

	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) remove(w http.ResponseWriter, r *http.Request) {
	method := "remove"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logReq(method, request.Name).Info("")

	// It is an error if the volume doesn't exist.
	_, err = d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	json.NewEncoder(w).Encode(&volumeResponse{})
}

func (d *driver) mount(w http.ResponseWriter, r *http.Request) {
	var response volumePathResponse
	method := "mount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logReq(method, "").Warn("Cannot locate volume driver")
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	d.logReq(method, request.Name).Debug("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	// If this is a block driver, first attach the volume.
	if v.Type()&api.Block != 0 {
		attachPath, err := v.Attach(vol.ID)
		if err != nil {
			d.logReq(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
			d.errorResponse(w, err)
			return
		}
		d.logReq(method, request.Name).Debugf("response %v", attachPath)
	}

	// Now mount it.
	response.Mountpoint = path.Join(config.MountBase, request.Name)
	os.MkdirAll(response.Mountpoint, 0755)

	err = v.Mount(vol.ID, response.Mountpoint)
	if err != nil {
		d.logReq(method, request.Name).Warnf("Cannot mount volume %v, %v",
			response.Mountpoint, err)
		d.errorResponse(w, err)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	os.MkdirAll(response.Mountpoint, 0755)

	d.logReq(method, request.Name).Infof("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) path(w http.ResponseWriter, r *http.Request) {
	method := "path"
	var response volumePathResponse

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	d.logReq(method, request.Name).Debug("")
	response.Mountpoint = vol.AttachPath
	if response.Mountpoint == "" {
		e := d.volNotMounted(method, request.Name)
		d.errorResponse(w, e)
		return
	}
	response.Mountpoint = path.Join(response.Mountpoint, config.DataDir)
	d.logReq(method, request.Name).Debugf("response %v", response.Mountpoint)
	json.NewEncoder(w).Encode(&response)
}

func (d *driver) list(w http.ResponseWriter, r *http.Request) {
	method := "list"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logReq(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	vols, err := v.Enumerate(api.VolumeLocator{}, nil)
	if err != nil {
		d.errorResponse(w, err)
		return
	}

	volInfo := make([]volumeInfo, len(vols))
	for i, v := range vols {
		volInfo[i].Name = v.Locator.Name
		if v.AttachPath != "" {
			volInfo[i].Mountpoint = path.Join(v.AttachPath, config.DataDir)
		}
	}
	json.NewEncoder(w).Encode(map[string][]volumeInfo{"Volumes": volInfo})
}

func (d *driver) get(w http.ResponseWriter, r *http.Request) {
	method := "get"

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}
	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	volInfo := volumeInfo{Name: request.Name}
	if vol.AttachPath != "" {
		volInfo.Mountpoint = path.Join(vol.AttachPath, config.DataDir)
	}

	json.NewEncoder(w).Encode(map[string]volumeInfo{"Volume": volInfo})
}

func (d *driver) unmount(w http.ResponseWriter, r *http.Request) {
	method := "unmount"

	v, err := volume.Get(d.name)
	if err != nil {
		d.logReq(method, "").Warnf("Cannot locate volume driver: %v", err.Error())
		d.errorResponse(w, err)
		return
	}

	request, err := d.decode(method, w, r)
	if err != nil {
		return
	}

	d.logReq(method, request.Name).Info("")

	vol, err := d.volFromName(request.Name)
	if err != nil {
		e := d.volNotFound(method, request.Name, err, w)
		d.errorResponse(w, e)
		return
	}

	mountpoint := path.Join(config.MountBase, request.Name)
	err = v.Unmount(vol.ID, mountpoint)
	if err != nil {
		d.logReq(method, request.Name).Warnf("Cannot unmount volume %v, %v",
			mountpoint, err)
		d.errorResponse(w, err)
		return
	}

	if v.Type()&api.Block != 0 {
		_ = v.Detach(vol.ID)
	}
	d.emptyResponse(w)
}
<|endoftext|>