<commit_before>// Copyright (c) 2018 Huawei Technologies Co., Ltd. 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 opensds

import (
	"fmt"
	"runtime"
	"strings"

	csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
	"github.com/golang/glog"
	sdscontroller "github.com/opensds/nbp/client/opensds"
	"github.com/opensds/nbp/csi/util"
	"github.com/opensds/opensds/pkg/model"
	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

////////////////////////////////////////////////////////////////////////////////
//                            Controller Service                              //
////////////////////////////////////////////////////////////////////////////////

// CreateVolume implementation
func (p *Plugin) CreateVolume(
	ctx context.Context,
	req *csi.CreateVolumeRequest) (
	*csi.CreateVolumeResponse, error) {

	glog.Info("start to CreateVolume")
	defer glog.Info("end to CreateVolume")

	c := sdscontroller.GetClient("", "")

	// build volume body
	volumebody := &model.VolumeSpec{}
	volumebody.Name = req.Name
	if req.CapacityRange != nil {
		volumeSizeBytes := int64(req.CapacityRange.RequiredBytes)
		allocationUnitBytes := int64(1024 * 1024 * 1024)
		volumebody.Size = (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes
		if volumebody.Size < 1 {
			//Using default volume size
			volumebody.Size = 1
		}
	} else {
		//Using default volume size
		volumebody.Size = 1
	}
	var secondaryAZ = util.OpensdsDefaultSecondaryAZ
	var enableReplication = false
	for k, v := range req.GetParameters() {
		switch strings.ToLower(k) {
		case KParamProfile:
			volumebody.ProfileId = v
		case KParamAZ:
			volumebody.AvailabilityZone = v
		case KParamEnableReplication:
			if strings.ToLower(v) == "true" {
				enableReplication = true
			}
		case KParamSecondaryAZ:
			secondaryAZ = v
		}
	}

	glog.Infof("CreateVolume volumebody: %v", volumebody)

	v, err := c.CreateVolume(volumebody)
	if err != nil {
		glog.Fatalf("failed to CreateVolume: %v", err)
		return nil, err
	}

	// return volume info
	volumeinfo := &csi.Volume{
		CapacityBytes: v.Size,
		Id:            v.Id,
		Attributes: map[string]string{
			KVolumeName:      v.Name,
			KVolumeStatus:    v.Status,
			KVolumeAZ:        v.AvailabilityZone,
			KVolumePoolId:    v.PoolId,
			KVolumeProfileId: v.ProfileId,
			KVolumeLvPath:    v.Metadata["lvPath"],
		},
	}

	if enableReplication {
		volumebody.AvailabilityZone = secondaryAZ
		volumebody.Name = SecondaryPrefix + req.Name
		sVol, err := c.CreateVolume(volumebody)
		if err != nil {
			glog.Errorf("failed to create secondar volume: %v", err)
			return nil, err
		}
		replicaBody := &model.ReplicationSpec{
			Name:              req.Name,
			PrimaryVolumeId:   v.Id,
			SecondaryVolumeId: sVol.Id,
			ReplicationMode:   model.ReplicationModeSync,
			ReplicationPeriod: 0,
		}
		replicaResp, err := c.CreateReplication(replicaBody)
		if err != nil {
			glog.Errorf("Create replication failed,:%v", err)
			return nil, err
		}
		volumeinfo.Attributes[KVolumeReplicationId] = replicaResp.Id
	}

	return &csi.CreateVolumeResponse{
		Volume: volumeinfo,
	}, nil
}

func getReplicationByVolume(volId string) *model.ReplicationSpec {
	c := sdscontroller.GetClient("","")
	replications, _ := c.ListReplications()
	for _, r := range replications {
		if volId == r.PrimaryVolumeId || volId == r.SecondaryVolumeId {
			return r
		}
	}
	return nil
}

// DeleteVolume implementation
func (p *Plugin) DeleteVolume(
	ctx context.Context,
	req *csi.DeleteVolumeRequest) (
	*csi.DeleteVolumeResponse, error) {
	glog.Info("start to DeleteVolume")
	defer glog.Info("end to DeleteVolume")
	volId := req.VolumeId
	c := sdscontroller.GetClient("", "")
	r := getReplicationByVolume(volId)
	if r != nil {
		if err := c.DeleteReplication(r.Id, nil); err != nil {
			return nil, err
		}
		if err := c.DeleteVolume(r.PrimaryVolumeId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
		if err := c.DeleteVolume(r.SecondaryVolumeId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
	} else {
		if err := c.DeleteVolume(volId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
	}

	return &csi.DeleteVolumeResponse{}, nil
}

// ControllerPublishVolume implementation
func (p *Plugin) ControllerPublishVolume(
	ctx context.Context,
	req *csi.ControllerPublishVolumeRequest) (
	*csi.ControllerPublishVolumeResponse, error) {

	glog.Info("start to ControllerPublishVolume")
	defer glog.Info("end to ControllerPublishVolume")

	client := sdscontroller.GetClient("", "")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	//TODO: need to check if node exists?

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish volume.")
	}

	var attachNodes []string
	hostname := req.NodeId
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && attachSpec.Host != hostname {
			//TODO: node id is what? use hostname to indicate node id currently.
			attachNodes = append(attachNodes, attachSpec.Host)
		}
	}

	if len(attachNodes) != 0 {
		//if the volume has been published, but without MULTI_NODE capability, return error.
		mode := req.VolumeCapability.AccessMode.Mode
		if mode != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER {
			msg := fmt.Sprintf("the volume %s has been published to another node.", req.VolumeId)
			return nil, status.Error(codes.AlreadyExists, msg)
		}
	}

	/*iqns, _ := iscsi.GetInitiator()
	localIqn := ""
	if len(iqns) > 0 {
		localIqn = iqns[0]
	}*/
	//NodeId is Node Iqn
	localIqn := req.NodeId

	attachReq := &model.VolumeAttachmentSpec{
		VolumeId: req.VolumeId,
		HostInfo: model.HostInfo{
			Host:      req.NodeId,
			Platform:  runtime.GOARCH,
			OsType:    runtime.GOOS,
			Initiator: localIqn,
		},
		Metadata: req.VolumeAttributes,
	}
	attachSpec, errAttach := client.CreateVolumeAttachment(attachReq)
	if errAttach != nil {
		msg := fmt.Sprintf("the volume %s failed to publish to node %s.", req.VolumeId, req.NodeId)
		glog.Errorf("failed to ControllerPublishVolume: %v", attachReq)
		return nil, status.Error(codes.FailedPrecondition, msg)
	}

	resp := &csi.ControllerPublishVolumeResponse{
		PublishInfo: map[string]string{
			KPublishHostIp:       attachSpec.Ip,
			KPublishHostName:     attachSpec.Host,
			KPublishAttachId:     attachSpec.Id,
			KPublishAttachStatus: attachSpec.Status,
		},
	}
	if replicationId, ok := req.VolumeAttributes[KVolumeReplicationId]; ok {
		r, err := client.GetReplication(replicationId)
		if err != nil {
			return nil, status.Error(codes.FailedPrecondition, "Get replication failed")
		}
		attachReq.VolumeId = r.SecondaryVolumeId
		attachSpec, errAttach := client.CreateVolumeAttachment(attachReq)
		if errAttach != nil {
			msg := fmt.Sprintf("the volume %s failed to publish to node %s.", req.VolumeId, req.NodeId)
			glog.Errorf("failed to ControllerPublishVolume: %v", attachReq)
			return nil, status.Error(codes.FailedPrecondition, msg)
		}
		resp.PublishInfo[KPublishSecondaryAttachId] = attachSpec.Id
	}
	return resp, nil
}

// ControllerUnpublishVolume implementation
func (p *Plugin) ControllerUnpublishVolume(
	ctx context.Context,
	req *csi.ControllerUnpublishVolumeRequest) (
	*csi.ControllerUnpublishVolumeResponse, error) {

	glog.Info("start to ControllerUnpublishVolume")
	defer glog.Info("end to ControllerUnpublishVolume")

	client := sdscontroller.GetClient("", "")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to unpublish volume.")
	}

	var acts []*model.VolumeAttachmentSpec
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && (req.NodeId == "" || attachSpec.Host == req.NodeId) {
			acts = append(acts, attachSpec)
		}
	}

	if r := getReplicationByVolume(req.VolumeId); r != nil {
		for _, attachSpec := range attachments {
			if attachSpec.VolumeId == r.SecondaryVolumeId && (req.NodeId == "" || attachSpec.Host == req.NodeId) {
				acts = append(acts, attachSpec)
			}
		}
	}
	for _, act := range acts {
		err = client.DeleteVolumeAttachment(act.Id, act)
		if err != nil {
			msg := fmt.Sprintf("the volume %s failed to unpublish from node %s.", req.VolumeId, req.NodeId)
			glog.Errorf("failed to ControllerUnpublishVolume: %v", err)
			return nil, status.Error(codes.FailedPrecondition, msg)
		}
	}

	return &csi.ControllerUnpublishVolumeResponse{}, nil
}

// ValidateVolumeCapabilities implementation
func (p *Plugin) ValidateVolumeCapabilities(
	ctx context.Context,
	req *csi.ValidateVolumeCapabilitiesRequest) (
	*csi.ValidateVolumeCapabilitiesResponse, error) {

	glog.Info("start to ValidateVolumeCapabilities")
	defer glog.Info("end to ValidateVolumeCapabilities")

	if strings.TrimSpace(req.VolumeId) == "" {
		// csi.Error_ValidateVolumeCapabilitiesError_INVALID_VOLUME_INFO
		return nil, status.Error(codes.NotFound, "invalid volume id")
	}

	for _, capabilities := range req.VolumeCapabilities {
		if capabilities.GetMount() != nil {
			return &csi.ValidateVolumeCapabilitiesResponse{
				Supported: false,
				Message:   "opensds does not support mounted volume",
			}, nil
		}
	}

	return &csi.ValidateVolumeCapabilitiesResponse{
		Supported: true,
		Message:   "supported",
	}, nil
}

// ListVolumes implementation
func (p *Plugin) ListVolumes(
	ctx context.Context,
	req *csi.ListVolumesRequest) (
	*csi.ListVolumesResponse, error) {

	glog.Info("start to ListVolumes")
	defer glog.Info("end to ListVolumes")

	c := sdscontroller.GetClient("", "")

	// only support list all the volumes at present
	volumes, err := c.ListVolumes()
	if err != nil {
		return nil, err
	}

	ens := []*csi.ListVolumesResponse_Entry{}
	for _, v := range volumes {
		if v != nil {

			volumeinfo := &csi.Volume{
				CapacityBytes: v.Size,
				Id:            v.Id,
				Attributes: map[string]string{
					"Name":             v.Name,
					"Status":           v.Status,
					"AvailabilityZone": v.AvailabilityZone,
					"PoolId":           v.PoolId,
					"ProfileId":        v.ProfileId,
				},
			}

			ens = append(ens, &csi.ListVolumesResponse_Entry{
				Volume: volumeinfo,
			})
		}
	}

	return &csi.ListVolumesResponse{
		Entries: ens,
	}, nil
}

// GetCapacity implementation
func (p *Plugin) GetCapacity(
	ctx context.Context,
	req *csi.GetCapacityRequest) (
	*csi.GetCapacityResponse, error) {

	glog.Info("start to GetCapacity")
	defer glog.Info("end to GetCapacity")

	c := sdscontroller.GetClient("", "")

	pools, err := c.ListPools()
	if err != nil {
		return nil, err
	}

	// calculate all the free capacity of pools
	freecapacity := int64(0)
	for _, p := range pools {
		if p != nil {
			freecapacity += int64(p.FreeCapacity)
		}
	}

	return &csi.GetCapacityResponse{
		AvailableCapacity: freecapacity,
	}, nil
}

// ControllerGetCapabilities implementation
func (p *Plugin) ControllerGetCapabilities(
	ctx context.Context,
	req *csi.ControllerGetCapabilitiesRequest) (
	*csi.ControllerGetCapabilitiesResponse, error) {

	glog.Info("start to ControllerGetCapabilities")
	defer glog.Info("end to ControllerGetCapabilities")

	return &csi.ControllerGetCapabilitiesResponse{
		Capabilities: []*csi.ControllerServiceCapability{
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_GET_CAPACITY,
					},
				},
			},
		},
	}, nil
}
<commit_msg>fixed a wrong calculation about volumeinfo<commit_after>// Copyright (c) 2018 Huawei Technologies Co., Ltd. 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 opensds

import (
	"fmt"
	"runtime"
	"strings"

	csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
	"github.com/golang/glog"
	sdscontroller "github.com/opensds/nbp/client/opensds"
	"github.com/opensds/nbp/csi/util"
	"github.com/opensds/opensds/pkg/model"
	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

////////////////////////////////////////////////////////////////////////////////
//                            Controller Service                              //
////////////////////////////////////////////////////////////////////////////////

// CreateVolume implementation
func (p *Plugin) CreateVolume(
	ctx context.Context,
	req *csi.CreateVolumeRequest) (
	*csi.CreateVolumeResponse, error) {

	glog.Info("start to CreateVolume")
	defer glog.Info("end to CreateVolume")

	c := sdscontroller.GetClient("", "")

	// build volume body
	volumebody := &model.VolumeSpec{}
	volumebody.Name = req.Name
	allocationUnitBytes := int64(1024 * 1024 * 1024)
	if req.CapacityRange != nil {
		volumeSizeBytes := int64(req.CapacityRange.RequiredBytes)
		volumebody.Size = (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes
		if volumebody.Size < 1 {
			//Using default volume size
			volumebody.Size = 1
		}
	} else {
		//Using default volume size
		volumebody.Size = 1
	}
	var secondaryAZ = util.OpensdsDefaultSecondaryAZ
	var enableReplication = false
	for k, v := range req.GetParameters() {
		switch strings.ToLower(k) {
		case KParamProfile:
			volumebody.ProfileId = v
		case KParamAZ:
			volumebody.AvailabilityZone = v
		case KParamEnableReplication:
			if strings.ToLower(v) == "true" {
				enableReplication = true
			}
		case KParamSecondaryAZ:
			secondaryAZ = v
		}
	}

	glog.Infof("CreateVolume volumebody: %v", volumebody)

	v, err := c.CreateVolume(volumebody)
	if err != nil {
		glog.Fatalf("failed to CreateVolume: %v", err)
		return nil, err
	}

	// return volume info
	volumeinfo := &csi.Volume{
		CapacityBytes: v.Size * allocationUnitBytes,
		Id:            v.Id,
		Attributes: map[string]string{
			KVolumeName:      v.Name,
			KVolumeStatus:    v.Status,
			KVolumeAZ:        v.AvailabilityZone,
			KVolumePoolId:    v.PoolId,
			KVolumeProfileId: v.ProfileId,
			KVolumeLvPath:    v.Metadata["lvPath"],
		},
	}

	if enableReplication {
		volumebody.AvailabilityZone = secondaryAZ
		volumebody.Name = SecondaryPrefix + req.Name
		sVol, err := c.CreateVolume(volumebody)
		if err != nil {
			glog.Errorf("failed to create secondar volume: %v", err)
			return nil, err
		}
		replicaBody := &model.ReplicationSpec{
			Name:              req.Name,
			PrimaryVolumeId:   v.Id,
			SecondaryVolumeId: sVol.Id,
			ReplicationMode:   model.ReplicationModeSync,
			ReplicationPeriod: 0,
		}
		replicaResp, err := c.CreateReplication(replicaBody)
		if err != nil {
			glog.Errorf("Create replication failed,:%v", err)
			return nil, err
		}
		volumeinfo.Attributes[KVolumeReplicationId] = replicaResp.Id
	}

	return &csi.CreateVolumeResponse{
		Volume: volumeinfo,
	}, nil
}

func getReplicationByVolume(volId string) *model.ReplicationSpec {
	c := sdscontroller.GetClient("","")
	replications, _ := c.ListReplications()
	for _, r := range replications {
		if volId == r.PrimaryVolumeId || volId == r.SecondaryVolumeId {
			return r
		}
	}
	return nil
}

// DeleteVolume implementation
func (p *Plugin) DeleteVolume(
	ctx context.Context,
	req *csi.DeleteVolumeRequest) (
	*csi.DeleteVolumeResponse, error) {
	glog.Info("start to DeleteVolume")
	defer glog.Info("end to DeleteVolume")
	volId := req.VolumeId
	c := sdscontroller.GetClient("", "")
	r := getReplicationByVolume(volId)
	if r != nil {
		if err := c.DeleteReplication(r.Id, nil); err != nil {
			return nil, err
		}
		if err := c.DeleteVolume(r.PrimaryVolumeId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
		if err := c.DeleteVolume(r.SecondaryVolumeId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
	} else {
		if err := c.DeleteVolume(volId, &model.VolumeSpec{}); err != nil {
			return nil, err
		}
	}

	return &csi.DeleteVolumeResponse{}, nil
}

// ControllerPublishVolume implementation
func (p *Plugin) ControllerPublishVolume(
	ctx context.Context,
	req *csi.ControllerPublishVolumeRequest) (
	*csi.ControllerPublishVolumeResponse, error) {

	glog.Info("start to ControllerPublishVolume")
	defer glog.Info("end to ControllerPublishVolume")

	client := sdscontroller.GetClient("", "")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	//TODO: need to check if node exists?

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish volume.")
	}

	var attachNodes []string
	hostname := req.NodeId
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && attachSpec.Host != hostname {
			//TODO: node id is what? use hostname to indicate node id currently.
			attachNodes = append(attachNodes, attachSpec.Host)
		}
	}

	if len(attachNodes) != 0 {
		//if the volume has been published, but without MULTI_NODE capability, return error.
		mode := req.VolumeCapability.AccessMode.Mode
		if mode != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER {
			msg := fmt.Sprintf("the volume %s has been published to another node.", req.VolumeId)
			return nil, status.Error(codes.AlreadyExists, msg)
		}
	}

	/*iqns, _ := iscsi.GetInitiator()
	localIqn := ""
	if len(iqns) > 0 {
		localIqn = iqns[0]
	}*/
	//NodeId is Node Iqn
	localIqn := req.NodeId

	attachReq := &model.VolumeAttachmentSpec{
		VolumeId: req.VolumeId,
		HostInfo: model.HostInfo{
			Host:      req.NodeId,
			Platform:  runtime.GOARCH,
			OsType:    runtime.GOOS,
			Initiator: localIqn,
		},
		Metadata: req.VolumeAttributes,
	}
	attachSpec, errAttach := client.CreateVolumeAttachment(attachReq)
	if errAttach != nil {
		msg := fmt.Sprintf("the volume %s failed to publish to node %s.", req.VolumeId, req.NodeId)
		glog.Errorf("failed to ControllerPublishVolume: %v", attachReq)
		return nil, status.Error(codes.FailedPrecondition, msg)
	}

	resp := &csi.ControllerPublishVolumeResponse{
		PublishInfo: map[string]string{
			KPublishHostIp:       attachSpec.Ip,
			KPublishHostName:     attachSpec.Host,
			KPublishAttachId:     attachSpec.Id,
			KPublishAttachStatus: attachSpec.Status,
		},
	}
	if replicationId, ok := req.VolumeAttributes[KVolumeReplicationId]; ok {
		r, err := client.GetReplication(replicationId)
		if err != nil {
			return nil, status.Error(codes.FailedPrecondition, "Get replication failed")
		}
		attachReq.VolumeId = r.SecondaryVolumeId
		attachSpec, errAttach := client.CreateVolumeAttachment(attachReq)
		if errAttach != nil {
			msg := fmt.Sprintf("the volume %s failed to publish to node %s.", req.VolumeId, req.NodeId)
			glog.Errorf("failed to ControllerPublishVolume: %v", attachReq)
			return nil, status.Error(codes.FailedPrecondition, msg)
		}
		resp.PublishInfo[KPublishSecondaryAttachId] = attachSpec.Id
	}
	return resp, nil
}

// ControllerUnpublishVolume implementation
func (p *Plugin) ControllerUnpublishVolume(
	ctx context.Context,
	req *csi.ControllerUnpublishVolumeRequest) (
	*csi.ControllerUnpublishVolumeResponse, error) {

	glog.Info("start to ControllerUnpublishVolume")
	defer glog.Info("end to ControllerUnpublishVolume")

	client := sdscontroller.GetClient("", "")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to unpublish volume.")
	}

	var acts []*model.VolumeAttachmentSpec
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && (req.NodeId == "" || attachSpec.Host == req.NodeId) {
			acts = append(acts, attachSpec)
		}
	}

	if r := getReplicationByVolume(req.VolumeId); r != nil {
		for _, attachSpec := range attachments {
			if attachSpec.VolumeId == r.SecondaryVolumeId && (req.NodeId == "" || attachSpec.Host == req.NodeId) {
				acts = append(acts, attachSpec)
			}
		}
	}
	for _, act := range acts {
		err = client.DeleteVolumeAttachment(act.Id, act)
		if err != nil {
			msg := fmt.Sprintf("the volume %s failed to unpublish from node %s.", req.VolumeId, req.NodeId)
			glog.Errorf("failed to ControllerUnpublishVolume: %v", err)
			return nil, status.Error(codes.FailedPrecondition, msg)
		}
	}

	return &csi.ControllerUnpublishVolumeResponse{}, nil
}

// ValidateVolumeCapabilities implementation
func (p *Plugin) ValidateVolumeCapabilities(
	ctx context.Context,
	req *csi.ValidateVolumeCapabilitiesRequest) (
	*csi.ValidateVolumeCapabilitiesResponse, error) {

	glog.Info("start to ValidateVolumeCapabilities")
	defer glog.Info("end to ValidateVolumeCapabilities")

	if strings.TrimSpace(req.VolumeId) == "" {
		// csi.Error_ValidateVolumeCapabilitiesError_INVALID_VOLUME_INFO
		return nil, status.Error(codes.NotFound, "invalid volume id")
	}

	for _, capabilities := range req.VolumeCapabilities {
		if capabilities.GetMount() != nil {
			return &csi.ValidateVolumeCapabilitiesResponse{
				Supported: false,
				Message:   "opensds does not support mounted volume",
			}, nil
		}
	}

	return &csi.ValidateVolumeCapabilitiesResponse{
		Supported: true,
		Message:   "supported",
	}, nil
}

// ListVolumes implementation
func (p *Plugin) ListVolumes(
	ctx context.Context,
	req *csi.ListVolumesRequest) (
	*csi.ListVolumesResponse, error) {

	glog.Info("start to ListVolumes")
	defer glog.Info("end to ListVolumes")

	c := sdscontroller.GetClient("", "")

	// only support list all the volumes at present
	volumes, err := c.ListVolumes()
	if err != nil {
		return nil, err
	}

	ens := []*csi.ListVolumesResponse_Entry{}
	for _, v := range volumes {
		if v != nil {

			volumeinfo := &csi.Volume{
				CapacityBytes: v.Size,
				Id:            v.Id,
				Attributes: map[string]string{
					"Name":             v.Name,
					"Status":           v.Status,
					"AvailabilityZone": v.AvailabilityZone,
					"PoolId":           v.PoolId,
					"ProfileId":        v.ProfileId,
				},
			}

			ens = append(ens, &csi.ListVolumesResponse_Entry{
				Volume: volumeinfo,
			})
		}
	}

	return &csi.ListVolumesResponse{
		Entries: ens,
	}, nil
}

// GetCapacity implementation
func (p *Plugin) GetCapacity(
	ctx context.Context,
	req *csi.GetCapacityRequest) (
	*csi.GetCapacityResponse, error) {

	glog.Info("start to GetCapacity")
	defer glog.Info("end to GetCapacity")

	c := sdscontroller.GetClient("", "")

	pools, err := c.ListPools()
	if err != nil {
		return nil, err
	}

	// calculate all the free capacity of pools
	freecapacity := int64(0)
	for _, p := range pools {
		if p != nil {
			freecapacity += int64(p.FreeCapacity)
		}
	}

	return &csi.GetCapacityResponse{
		AvailableCapacity: freecapacity,
	}, nil
}

// ControllerGetCapabilities implementation
func (p *Plugin) ControllerGetCapabilities(
	ctx context.Context,
	req *csi.ControllerGetCapabilitiesRequest) (
	*csi.ControllerGetCapabilitiesResponse, error) {

	glog.Info("start to ControllerGetCapabilities")
	defer glog.Info("end to ControllerGetCapabilities")

	return &csi.ControllerGetCapabilitiesResponse{
		Capabilities: []*csi.ControllerServiceCapability{
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
					},
				},
			},
			&csi.ControllerServiceCapability{
				Type: &csi.ControllerServiceCapability_Rpc{
					Rpc: &csi.ControllerServiceCapability_RPC{
						Type: csi.ControllerServiceCapability_RPC_GET_CAPACITY,
					},
				},
			},
		},
	}, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 kube

import (
	"github.com/prometheus/client_golang/prometheus"

	prowapi "k8s.io/test-infra/prow/apis/prowjobs/v1"
)

var (
	metricLabels = []string{
		// namespace of the job
		"job_namespace",
		// name of the job
		"job_name",
		// type of the prowjob: presubmit, postsubmit, periodic, batch
		"type",
		// state of the prowjob: triggered, pending, success, failure, aborted, error
		"state",
		// the org of the prowjob's repo
		"org",
		// the prowjob's repo
		"repo",
		// the base_ref of the prowjob's repo
		"base_ref",
	}
	prowJobs = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "prowjobs",
		Help: "Number of prowjobs in the system",
	}, metricLabels)
	prowJobTransitions = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "prowjob_state_transitions",
		Help: "Number of prowjobs transitioning states",
	}, metricLabels)
)

type jobLabel struct {
	jobNamespace string
	jobName      string
	jobType      string
	state        string
	org          string
	repo         string
	baseRef      string
}

func (jl *jobLabel) values() []string {
	return []string{jl.jobNamespace, jl.jobName, jl.jobType, jl.state, jl.org, jl.repo, jl.baseRef}
}

func init() {
	prometheus.MustRegister(prowJobs)
	prometheus.MustRegister(prowJobTransitions)
}

func getJobLabelMap(pjs []prowapi.ProwJob) map[jobLabel]float64 {
	jobLabelMap := make(map[jobLabel]float64)

	for _, pj := range pjs {
		jobLabelMap[getJobLabel(pj)]++
	}
	return jobLabelMap
}

func getJobLabel(pj prowapi.ProwJob) jobLabel {
	jl := jobLabel{jobName: pj.Spec.Job, jobType: string(pj.Spec.Type), state: string(pj.Status.State)}

	if pj.Spec.Refs != nil {
		jl.jobNamespace = pj.Namespace
		jl.org = pj.Spec.Refs.Org
		jl.repo = pj.Spec.Refs.Repo
		jl.baseRef = pj.Spec.Refs.BaseRef
	} else if len(pj.Spec.ExtraRefs) > 0 {
		jl.org = pj.Spec.ExtraRefs[0].Org
		jl.repo = pj.Spec.ExtraRefs[0].Repo
		jl.baseRef = pj.Spec.ExtraRefs[0].BaseRef
	}

	return jl
}

type jobIdentifier struct {
	jobLabel
	buildId string
}

func getJobIdentifier(pj prowapi.ProwJob) jobIdentifier {
	return jobIdentifier{
		jobLabel: getJobLabel(pj),
		buildId:  pj.Status.BuildID,
	}
}

// previousStates records the prowJobs we were called with previously
var previousStates map[jobIdentifier]prowapi.ProwJobState

// GatherProwJobMetrics gathers prometheus metrics for prowjobs.
// Not threadsafe, ensure this is called serially.
func GatherProwJobMetrics(current []prowapi.ProwJob) {
	// This may be racing with the prometheus server but we need to remove
	// stale metrics like triggered or pending jobs that are now complete.
	prowJobs.Reset()

	// record the current state of ProwJob CRs on the system
	for jl, count := range getJobLabelMap(current) {
		prowJobs.WithLabelValues(jl.values()...).Set(count)
	}

	// record state transitions since the last time we were called
	currentStates := map[jobIdentifier]prowapi.ProwJobState{}
	for _, pj := range current {
		ji := getJobIdentifier(pj)
		state := prowapi.ProwJobState(ji.state)
		currentStates[ji] = state

		if previousState, seen := previousStates[ji]; !seen || previousState != state {
			prowJobTransitions.WithLabelValues(ji.values()...).Inc()
		}
	}

	previousStates = currentStates
}
<commit_msg>Fix namespace label in kube metrics<commit_after>/*
Copyright 2017 The Kubernetes 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 kube

import (
	"github.com/prometheus/client_golang/prometheus"

	prowapi "k8s.io/test-infra/prow/apis/prowjobs/v1"
)

var (
	metricLabels = []string{
		// namespace of the job
		"job_namespace",
		// name of the job
		"job_name",
		// type of the prowjob: presubmit, postsubmit, periodic, batch
		"type",
		// state of the prowjob: triggered, pending, success, failure, aborted, error
		"state",
		// the org of the prowjob's repo
		"org",
		// the prowjob's repo
		"repo",
		// the base_ref of the prowjob's repo
		"base_ref",
	}
	prowJobs = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "prowjobs",
		Help: "Number of prowjobs in the system",
	}, metricLabels)
	prowJobTransitions = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "prowjob_state_transitions",
		Help: "Number of prowjobs transitioning states",
	}, metricLabels)
)

type jobLabel struct {
	jobNamespace string
	jobName      string
	jobType      string
	state        string
	org          string
	repo         string
	baseRef      string
}

func (jl *jobLabel) values() []string {
	return []string{jl.jobNamespace, jl.jobName, jl.jobType, jl.state, jl.org, jl.repo, jl.baseRef}
}

func init() {
	prometheus.MustRegister(prowJobs)
	prometheus.MustRegister(prowJobTransitions)
}

func getJobLabelMap(pjs []prowapi.ProwJob) map[jobLabel]float64 {
	jobLabelMap := make(map[jobLabel]float64)

	for _, pj := range pjs {
		jobLabelMap[getJobLabel(pj)]++
	}
	return jobLabelMap
}

func getJobLabel(pj prowapi.ProwJob) jobLabel {
	jl := jobLabel{jobNamespace: pj.Namespace, jobName: pj.Spec.Job, jobType: string(pj.Spec.Type), state: string(pj.Status.State)}

	if pj.Spec.Refs != nil {
		jl.org = pj.Spec.Refs.Org
		jl.repo = pj.Spec.Refs.Repo
		jl.baseRef = pj.Spec.Refs.BaseRef
	} else if len(pj.Spec.ExtraRefs) > 0 {
		jl.org = pj.Spec.ExtraRefs[0].Org
		jl.repo = pj.Spec.ExtraRefs[0].Repo
		jl.baseRef = pj.Spec.ExtraRefs[0].BaseRef
	}

	return jl
}

type jobIdentifier struct {
	jobLabel
	buildId string
}

func getJobIdentifier(pj prowapi.ProwJob) jobIdentifier {
	return jobIdentifier{
		jobLabel: getJobLabel(pj),
		buildId:  pj.Status.BuildID,
	}
}

// previousStates records the prowJobs we were called with previously
var previousStates map[jobIdentifier]prowapi.ProwJobState

// GatherProwJobMetrics gathers prometheus metrics for prowjobs.
// Not threadsafe, ensure this is called serially.
func GatherProwJobMetrics(current []prowapi.ProwJob) {
	// This may be racing with the prometheus server but we need to remove
	// stale metrics like triggered or pending jobs that are now complete.
	prowJobs.Reset()

	// record the current state of ProwJob CRs on the system
	for jl, count := range getJobLabelMap(current) {
		prowJobs.WithLabelValues(jl.values()...).Set(count)
	}

	// record state transitions since the last time we were called
	currentStates := map[jobIdentifier]prowapi.ProwJobState{}
	for _, pj := range current {
		ji := getJobIdentifier(pj)
		state := prowapi.ProwJobState(ji.state)
		currentStates[ji] = state

		if previousState, seen := previousStates[ji]; !seen || previousState != state {
			prowJobTransitions.WithLabelValues(ji.values()...).Inc()
		}
	}

	previousStates = currentStates
}
<|endoftext|>


<commit_before>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gfile

import (
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/text/gstr"
	"os"
	"path/filepath"
	"sort"
)

const (
	// Max recursive depth for directory scanning.
	gMAX_SCAN_DEPTH = 100000
)

// ScanDir returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns,
// using the ',' symbol to separate multiple patterns.
func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
	isRecursive := false
	if len(recursive) > 0 {
		isRecursive = recursive[0]
	}
	list, err := doScanDir(0, path, pattern, isRecursive, nil)
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFunc returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	list, err := doScanDir(0, path, pattern, recursive, handler)
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFile returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns,
// using the ',' symbol to separate multiple patterns.
//
// Note that it returns only files, exclusive of directories.
func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, error) {
	isRecursive := false
	if len(recursive) > 0 {
		isRecursive = recursive[0]
	}
	list, err := doScanDir(0, path, pattern, isRecursive, func(path string) string {
		if IsDir(path) {
			return ""
		}
		return path
	})
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFileFunc returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
//
// Note that the parameter <path> for <handler> is not a directory but a file.
// It returns only files, exclusive of directories.
func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	list, err := doScanDir(0, path, pattern, recursive, func(path string) string {
		if IsDir(path) {
			return ""
		}
		return handler(path)
	})
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// doScanDir is an internal method which scans directory and returns the absolute path
// list of files that are not sorted.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func doScanDir(depth int, path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	if depth >= gMAX_SCAN_DEPTH {
		return nil, gerror.Newf("directory scanning exceeds max recursive depth: %d", gMAX_SCAN_DEPTH)
	}
	list := ([]string)(nil)
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	names, err := file.Readdirnames(-1)
	if err != nil {
		return nil, err
	}
	var (
		filePath = ""
		patterns = gstr.SplitAndTrim(pattern, ",")
	)
	for _, name := range names {
		filePath = path + Separator + name
		if IsDir(filePath) && recursive {
			array, _ := doScanDir(depth+1, filePath, pattern, true, handler)
			if len(array) > 0 {
				list = append(list, array...)
			}
		}
		// Handler filtering.
		if handler != nil {
			filePath = handler(filePath)
			if filePath == "" {
				continue
			}
		}
		// If it meets pattern, then add it to the result list.
		for _, p := range patterns {
			if match, err := filepath.Match(p, name); err == nil && match {
				filePath = Abs(filePath)
				if filePath != "" {
					list = append(list, filePath)
				}
			}
		}
	}
	return list, nil
}
<commit_msg>const name renaming<commit_after>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gfile

import (
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/text/gstr"
	"os"
	"path/filepath"
	"sort"
)

const (
	// Max recursive depth for directory scanning.
	maxScanDepth = 100000
)

// ScanDir returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns,
// using the ',' symbol to separate multiple patterns.
func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
	isRecursive := false
	if len(recursive) > 0 {
		isRecursive = recursive[0]
	}
	list, err := doScanDir(0, path, pattern, isRecursive, nil)
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFunc returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	list, err := doScanDir(0, path, pattern, recursive, handler)
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFile returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns,
// using the ',' symbol to separate multiple patterns.
//
// Note that it returns only files, exclusive of directories.
func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, error) {
	isRecursive := false
	if len(recursive) > 0 {
		isRecursive = recursive[0]
	}
	list, err := doScanDir(0, path, pattern, isRecursive, func(path string) string {
		if IsDir(path) {
			return ""
		}
		return path
	})
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// ScanDirFileFunc returns all sub-files with absolute paths of given <path>,
// It scans directory recursively if given parameter <recursive> is true.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
//
// Note that the parameter <path> for <handler> is not a directory but a file.
// It returns only files, exclusive of directories.
func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	list, err := doScanDir(0, path, pattern, recursive, func(path string) string {
		if IsDir(path) {
			return ""
		}
		return handler(path)
	})
	if err != nil {
		return nil, err
	}
	if len(list) > 0 {
		sort.Strings(list)
	}
	return list, nil
}

// doScanDir is an internal method which scans directory and returns the absolute path
// list of files that are not sorted.
//
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
// symbol to separate multiple patterns.
//
// The parameter <recursive> specifies whether scanning the <path> recursively, which
// means it scans its sub-files and appends the files path to result array if the sub-file
// is also a folder. It is false in default.
//
// The parameter <handler> specifies the callback function handling each sub-file path of
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
// string, or else it appends the sub-file path to result slice.
func doScanDir(depth int, path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
	if depth >= maxScanDepth {
		return nil, gerror.Newf("directory scanning exceeds max recursive depth: %d", maxScanDepth)
	}
	list := ([]string)(nil)
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	names, err := file.Readdirnames(-1)
	if err != nil {
		return nil, err
	}
	var (
		filePath = ""
		patterns = gstr.SplitAndTrim(pattern, ",")
	)
	for _, name := range names {
		filePath = path + Separator + name
		if IsDir(filePath) && recursive {
			array, _ := doScanDir(depth+1, filePath, pattern, true, handler)
			if len(array) > 0 {
				list = append(list, array...)
			}
		}
		// Handler filtering.
		if handler != nil {
			filePath = handler(filePath)
			if filePath == "" {
				continue
			}
		}
		// If it meets pattern, then add it to the result list.
		for _, p := range patterns {
			if match, err := filepath.Match(p, name); err == nil && match {
				filePath = Abs(filePath)
				if filePath != "" {
					list = append(list, filePath)
				}
			}
		}
	}
	return list, nil
}
<|endoftext|>


<commit_before>package logd

import (
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"sync/atomic"
	"time"
)

// UTC Time Layout string
const layout = "2015/04/01 12:00:00.000"

// LogLevel defines a int type represent the different supported loglevels
type LogLevel int

const (
	//NoLogging defines the level where logging is disabled
	NoLogging LogLevel = iota
	// InfoLevel represents the info log level
	InfoLevel
	// DebugLevel represents the debug log level
	DebugLevel
	// DataTraceLevel represents the data dump log level
	DataTraceLevel
	// TraceLevel represents the function trace log level
	TraceLevel
	// ErrorLevel represents the error log level
	ErrorLevel
	// NotSupportedLevel represents level that are not supported
	NotSupportedLevel
)

// association strings with specific log levels
var logLevelAssoc = map[LogLevel]string{
	1: "INFO",
	2: "DEBUG",
	3: "DATATRACE",
	4: "TRACE",
	5: "ERROR",
}

// Mode is used to represent the output format, user log or dev log
type Mode int

const (
	// User mode only requires a simple readable format
	User Mode = iota + 1
	// Dev mode only requires a an extended information regarding output
	Dev
	// NotSupportedMode output modes that have no supported
	NotSupportedMode
)

// logLevelModeAssoc provides a key:formatstring association for log mode
var logLevelModeAssoc = map[Mode]string{
	1: `Type: %s Level: %s Time: %s Context: %s Func: %s Message: %s`,
	2: `Type: %s Level: %s Time: %s Context: %s Func: %s Line: %s Message: %s`,
}

// basicFormatter formats out the output of the log
func basicFormatter(lg *Loggly, ctx interface{}, funcName, funcMeta, Message string, data ...interface{}) string {
	levelName := logLevelAssoc[lg.Level()]
	modeVal := logLevelModeAssoc[lg.Mode()]
	var ms string

	if atomic.LoadInt32(&lg.testMode) == 0 {
		ms = time.Date(2009, time.November, 10, 15, 0, 0, 0, time.UTC).UTC().Format(layout)
	} else {
		ms = time.Now().UTC().Format(layout)
	}

	if lg.Mode() == User {
		return fmt.Sprintf(modeVal, lg.logType, levelName, ctx, ms, funcName, fmt.Sprintf(Message, data...))
	}

	return fmt.Sprintf(modeVal, lg.logType, levelName, ctx, ms, funcName, funcMeta, fmt.Sprintf(Message, data...))
}

// Loggly provides a base logging structure that provides a simple but adequate logging mechanism which provides both human readable and machine readable code
type Loggly struct {
	log      *log.Logger
	logType  string
	ro       sync.RWMutex
	level    LogLevel
	mo       sync.RWMutex
	mode     Mode
	testMode int32
}

// New returns a new instance of Loggly with the currently set loglevel at 1
func New(t string, dev io.Writer) *Loggly {
	lg := Loggly{
		log:     log.New(dev, "", 0),
		logType: t,
		level:   1,
	}
	return &lg
}

// TestModeLog returns a new instance of Loggly with the currently set loglevel at 1
func TestModeLog(t string, dev io.Writer) *Loggly {
	lg := New(t, os.Stdout)
	atomic.StoreInt32(&lg.testMode, 1)
	return lg
}

// StdLog returns a new instance of Loggly with the output device set to stdout
func StdLog(t string) *Loggly {
	return New(t, os.Stdout)
}

// SwitchMode sets the current mode into log instance to the supplied mode instance
func (l *Loggly) SwitchMode(m Mode) {
	//if its not a mode we support, skip
	if m < 0 || m >= NotSupportedMode {
		return
	}
	l.mo.Lock()
	l.mode = m
	l.mo.Unlock()
}

// SwitchLevel sets the current level into log instance
func (l *Loggly) SwitchLevel(lvl LogLevel) {
	//if its not a level we support, skip
	if lvl < 0 || lvl >= NotSupportedLevel {
		return
	}
	l.ro.Lock()
	l.level = lvl
	l.ro.Unlock()
}

// Mode returns the current output mode
func (l *Loggly) Mode() (m Mode) {
	l.mo.RLock()
	m = l.mode
	l.mo.RUnlock()
	return
}

// Level returns the current log level
func (l *Loggly) Level() (lvl LogLevel) {
	l.ro.RLock()
	lvl = l.level
	l.ro.RUnlock()
	return
}

// Log provides the core logging function used by Loggly
func (l *Loggly) Log(ctx interface{}, level LogLevel, funcName, Message string, data ...interface{}) {
	// var format string
	if level >= l.Level() && level < NotSupportedLevel {
		// l.log.Printf(format,ctx,level,)
	}
}

// Logf provides the core logging function used by Loggly
func (l *Loggly) Logf(ctx interface{}, level LogLevel, funcName, Message string, data ...interface{}) {
}

// Info logs debug level info
func (l *Loggly) Info(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Infof logs debug level info
func (l *Loggly) Infof(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Error logs debug level info
func (l *Loggly) Error(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Errorf logs debug level info
func (l *Loggly) Errorf(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Debug logs debug level info
func (l *Loggly) Debug(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Debugf logs debug level info
func (l *Loggly) Debugf(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Formatter presents an interface where a data value provides its own format directive
type Formatter interface {
	Format() string
}

// DataTrace dumps down the log message included with a json formatted data sets
func (l *Loggly) DataTrace(ctx interface{}, funcName string, Message string, data Formatter) {
}

// DataTracef dumps down the log message included with a json formatted data sets
func (l *Loggly) DataTracef(ctx interface{}, funcName string, Message string, data Formatter, vals ...interface{}) {
}

// ByteFormatter turns a byte into a proper line string of hexdecimal digits
func ByteFormatter(b []byte) Formatter {
	return nil
}
<commit_msg>added json,yaml loaders test case<commit_after>package logd

import (
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"sync/atomic"
	"time"
)

// UTC Time Layout string
const layout = "2015/04/01 12:00:00.000"

// LogLevel defines a int type represent the different supported loglevels
type LogLevel int

const (
	//NoLogging defines the level where logging is disabled
	NoLogging LogLevel = iota
	// InfoLevel represents the info log level
	InfoLevel
	// DebugLevel represents the debug log level
	DebugLevel
	// DataTraceLevel represents the data dump log level
	DataTraceLevel
	// TraceLevel represents the function trace log level
	TraceLevel
	// ErrorLevel represents the error log level
	ErrorLevel
	// NotSupportedLevel represents level that are not supported
	NotSupportedLevel
)

// association strings with specific log levels
var logLevelAssoc = map[LogLevel]string{
	1: "INFO",
	2: "DEBUG",
	3: "DATATRACE",
	4: "TRACE",
	5: "ERROR",
}

// Mode is used to represent the output format, user log or dev log
type Mode int

// const (
// 	// User mode only requires a simple readable format
// 	User Mode = iota + 1
// 	// Dev mode only requires a an extended information regarding output
// 	Dev
// 	// NotSupportedMode output modes that have no supported
// 	NotSupportedMode
// )

// // logLevelModeAssoc provides a key:formatstring association for log mode
// var logLevelModeAssoc = map[Mode]string{
// 	1: `Type: %s Level: %s Time: %s Context: %s Func: %s Message: %s`,
// 	2: `Type: %s Level: %s Time: %s Context: %s Func: %s Message: %s`,
// }
var logFormat = `Type: %s Level: %s Time: %s Context: %s Func: %s Message: %s`

// basicFormatter formats out the output of the log
func basicFormatter(lg *Loggly, ctx interface{}, funcName, Message string, data ...interface{}) string {
	var ms string
	levelName := logLevelAssoc[lg.Level()]
	modeVal := logFormat

	if atomic.LoadInt32(&lg.testMode) == 0 {
		ms = time.Date(2009, time.November, 10, 15, 0, 0, 0, time.UTC).UTC().Format(layout)
	} else {
		ms = time.Now().UTC().Format(layout)
	}

	// if lg.Mode() == User {
	// 	return fmt.Sprintf(modeVal, lg.logType, levelName, ctx, ms, funcName, fmt.Sprintf(Message, data...))
	// }

	return fmt.Sprintf(modeVal, lg.logType, levelName, ctx, ms, funcName, fmt.Sprintf(Message, data...))
}

// Loggly provides a base logging structure that provides a simple but adequate logging mechanism which provides both human readable and machine readable code
type Loggly struct {
	log     *log.Logger
	logType string
	ro      sync.RWMutex
	level   LogLevel
	// mo       sync.RWMutex
	// mode     Mode
	testMode int32
}

// New returns a new instance of Loggly with the currently set loglevel at 1
func New(t string, dev io.Writer) *Loggly {
	lg := Loggly{
		log:     log.New(dev, "", 0),
		logType: t,
		level:   1,
	}
	return &lg
}

// TestModeLog returns a new instance of Loggly with the currently set loglevel at 1
func TestModeLog(t string, dev io.Writer) *Loggly {
	lg := New(t, os.Stdout)
	atomic.StoreInt32(&lg.testMode, 1)
	return lg
}

// StdLog returns a new instance of Loggly with the output device set to stdout
func StdLog(t string) *Loggly {
	return New(t, os.Stdout)
}

// // SwitchMode sets the current mode into log instance to the supplied mode instance
// func (l *Loggly) SwitchMode(m Mode) {
// 	//if its not a mode we support, skip
// 	if m < 0 || m >= NotSupportedMode {
// 		return
// 	}
// 	l.mo.Lock()
// 	l.mode = m
// 	l.mo.Unlock()
// }

// SwitchLevel sets the current level into log instance
func (l *Loggly) SwitchLevel(lvl LogLevel) {
	//if its not a level we support, skip
	if lvl < 0 || lvl >= NotSupportedLevel {
		return
	}
	l.ro.Lock()
	l.level = lvl
	l.ro.Unlock()
}

// // Mode returns the current output mode
// func (l *Loggly) Mode() (m Mode) {
// 	l.mo.RLock()
// 	m = l.mode
// 	l.mo.RUnlock()
// 	return
// }

// Level returns the current log level
func (l *Loggly) Level() (lvl LogLevel) {
	l.ro.RLock()
	lvl = l.level
	l.ro.RUnlock()
	return
}

// Log provides the core logging function used by Loggly
func (l *Loggly) Log(ctx interface{}, level LogLevel, funcName, Message string, data ...interface{}) {
	// var format string
	if level >= l.Level() && level < NotSupportedLevel {
		// l.log.Printf(format,ctx,level,)
	}
}

// Logf provides the core logging function used by Loggly
func (l *Loggly) Logf(ctx interface{}, level LogLevel, funcName, Message string, data ...interface{}) {
}

// Info logs debug level info
func (l *Loggly) Info(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Infof logs debug level info
func (l *Loggly) Infof(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Error logs debug level info
func (l *Loggly) Error(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Errorf logs debug level info
func (l *Loggly) Errorf(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Debug logs debug level info
func (l *Loggly) Debug(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Debugf logs debug level info
func (l *Loggly) Debugf(ctx interface{}, funcName, Message string, data ...interface{}) {}

// Formatter presents an interface where a data value provides its own format directive
type Formatter interface {
	Format() string
}

// DataTrace dumps down the log message included with a formatted data string respecting newlines
func (l *Loggly) DataTrace(ctx interface{}, funcName string, Message string, data Formatter) {
}

// DataTracef dumps down the log message included with a json formatted data sets
func (l *Loggly) DataTracef(ctx interface{}, funcName string, Message string, data Formatter, vals ...interface{}) {
}

// ByteFormatter turns a byte into a proper line string of hexdecimal digits
func ByteFormatter(b []byte) Formatter {
	return nil
}
<|endoftext|>


<commit_before>package messagebus

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"time"

	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/route-registrar/config"
	"github.com/nats-io/nats"
)

//go:generate counterfeiter . MessageBus

type MessageBus interface {
	Connect(servers []config.MessageBusServer) error
	SendMessage(subject string, host string, route config.Route, privateInstanceId string) error
	Close()
}

type msgBus struct {
	natsConn *nats.Conn
	logger   lager.Logger
}

type Message struct {
	URIs              []string          `json:"uris"`
	Host              string            `json:"host"`
	Port              int               `json:"port"`
	Tags              map[string]string `json:"tags"`
	RouteServiceUrl   string            `json:"route_service_url,omitempty"`
	PrivateInstanceId string            `json:"private_instance_id"`
}

func NewMessageBus(logger lager.Logger) MessageBus {
	return &msgBus{
		logger: logger,
	}
}

func (m *msgBus) Connect(servers []config.MessageBusServer) error {

	var natsServers []string
	for _, server := range servers {
		natsServers = append(
			natsServers,
			fmt.Sprintf("nats://%s:%s@%s", server.User, server.Password, server.Host),
		)
	}

	opts := nats.DefaultOptions
	opts.Servers = natsServers
	opts.PingInterval = 20 * time.Second

	opts.ClosedCB = func(conn *nats.Conn) {
		m.logger.Error("nats-connection-closed", errors.New("unexpected nats conn closed"))
	}

	opts.DisconnectedCB = func(conn *nats.Conn) {
		m.logger.Info("nats-connection-disconnected")
	}

	opts.ReconnectedCB = func(conn *nats.Conn) {
		natsHost, err := parseNatsUrl(conn.ConnectedUrl())
		if err != nil {
			m.logger.Error("nats-url-parse-failed", err, lager.Data{"nats-host": natsHost})
		}
		m.logger.Info("nats-connection-reconnected", lager.Data{"nats-host": natsHost})
	}

	natsConn, err := opts.Connect()
	if err != nil {
		m.logger.Error("nats-connection-failed", err)
		return err
	}

	natsHost, err := parseNatsUrl(natsConn.ConnectedUrl())
	if err != nil {
		m.logger.Error("nats-url-parse-failed", err, lager.Data{"nats-host": natsHost})
	}

	m.logger.Info("nats-connection-successfull", lager.Data{"nats-url": natsHost})
	m.natsConn = natsConn

	return nil
}

func (m msgBus) SendMessage(subject string, host string, route config.Route, privateInstanceId string) error {
	m.logger.Debug("Creating message", lager.Data{"subject": subject, "host": host, "route": route, "privateInstanceId": privateInstanceId})

	msg := &Message{
		URIs:              route.URIs,
		Host:              host,
		Port:              route.Port,
		Tags:              route.Tags,
		RouteServiceUrl:   route.RouteServiceUrl,
		PrivateInstanceId: privateInstanceId,
	}

	json, err := json.Marshal(msg)
	if err != nil {
		// Untested as we cannot force json.Marshal to return error.
		return err
	}

	m.logger.Debug("Publishing message", lager.Data{"msg": string(json)})

	return m.natsConn.Publish(subject, json)
}

func (m msgBus) Close() {
	m.natsConn.Close()
}

func parseNatsUrl(natsUrl string) (string, error) {
	natsURL, err := url.Parse(natsUrl)
	natsHostStr := ""
	if err != nil {
		return "", err
	} else {
		natsHostStr = natsURL.Host
	}

	return natsHostStr, nil
}
<commit_msg>Use hyphens instead of space in log msgs<commit_after>package messagebus

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"time"

	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/route-registrar/config"
	"github.com/nats-io/nats"
)

//go:generate counterfeiter . MessageBus

type MessageBus interface {
	Connect(servers []config.MessageBusServer) error
	SendMessage(subject string, host string, route config.Route, privateInstanceId string) error
	Close()
}

type msgBus struct {
	natsConn *nats.Conn
	logger   lager.Logger
}

type Message struct {
	URIs              []string          `json:"uris"`
	Host              string            `json:"host"`
	Port              int               `json:"port"`
	Tags              map[string]string `json:"tags"`
	RouteServiceUrl   string            `json:"route_service_url,omitempty"`
	PrivateInstanceId string            `json:"private_instance_id"`
}

func NewMessageBus(logger lager.Logger) MessageBus {
	return &msgBus{
		logger: logger,
	}
}

func (m *msgBus) Connect(servers []config.MessageBusServer) error {

	var natsServers []string
	for _, server := range servers {
		natsServers = append(
			natsServers,
			fmt.Sprintf("nats://%s:%s@%s", server.User, server.Password, server.Host),
		)
	}

	opts := nats.DefaultOptions
	opts.Servers = natsServers
	opts.PingInterval = 20 * time.Second

	opts.ClosedCB = func(conn *nats.Conn) {
		m.logger.Error("nats-connection-closed", errors.New("unexpected nats conn closed"))
	}

	opts.DisconnectedCB = func(conn *nats.Conn) {
		m.logger.Info("nats-connection-disconnected")
	}

	opts.ReconnectedCB = func(conn *nats.Conn) {
		natsHost, err := parseNatsUrl(conn.ConnectedUrl())
		if err != nil {
			m.logger.Error("nats-url-parse-failed", err, lager.Data{"nats-host": natsHost})
		}
		m.logger.Info("nats-connection-reconnected", lager.Data{"nats-host": natsHost})
	}

	natsConn, err := opts.Connect()
	if err != nil {
		m.logger.Error("nats-connection-failed", err)
		return err
	}

	natsHost, err := parseNatsUrl(natsConn.ConnectedUrl())
	if err != nil {
		m.logger.Error("nats-url-parse-failed", err, lager.Data{"nats-host": natsHost})
	}

	m.logger.Info("nats-connection-successfull", lager.Data{"nats-url": natsHost})
	m.natsConn = natsConn

	return nil
}

func (m msgBus) SendMessage(subject string, host string, route config.Route, privateInstanceId string) error {
	m.logger.Debug("creating-message", lager.Data{"subject": subject, "host": host, "route": route, "privateInstanceId": privateInstanceId})

	msg := &Message{
		URIs:              route.URIs,
		Host:              host,
		Port:              route.Port,
		Tags:              route.Tags,
		RouteServiceUrl:   route.RouteServiceUrl,
		PrivateInstanceId: privateInstanceId,
	}

	json, err := json.Marshal(msg)
	if err != nil {
		// Untested as we cannot force json.Marshal to return error.
		return err
	}

	m.logger.Debug("publishing-message", lager.Data{"msg": string(json)})

	return m.natsConn.Publish(subject, json)
}

func (m msgBus) Close() {
	m.natsConn.Close()
}

func parseNatsUrl(natsUrl string) (string, error) {
	natsURL, err := url.Parse(natsUrl)
	natsHostStr := ""
	if err != nil {
		return "", err
	} else {
		natsHostStr = natsURL.Host
	}

	return natsHostStr, nil
}
<|endoftext|>


<commit_before>package scipipe

const (
	// BUFSIZE is the standard buffer size used for channels connecting processes
	BUFSIZE = 16
	// Version is the SciPipe version in string format
	Version = "0.9.1"
)
<commit_msg>Bump version to 0.9.2<commit_after>package scipipe

const (
	// BUFSIZE is the standard buffer size used for channels connecting processes
	BUFSIZE = 16
	// Version is the SciPipe version in string format
	Version = "0.9.2"
)
<|endoftext|>


<commit_before>package derived

import (
	"fmt"
	"github.com/kubernetes-incubator/kube-aws/model"
)

type EtcdNode interface {
	AdvertisedFQDNRef() (string, error)
	DependencyExists() bool
	DependencyRef() (string, error)
	EBSLogicalName() string
	EBSRef() string
	EIPAllocationIDRef() (string, error)
	EIPLogicalName() (string, error)
	EIPManaged() bool
	EIPRef() (string, error)
	// The name of the etcd member runs on this etcd node
	Name() string
	NetworkInterfaceIDRef() string
	NetworkInterfaceLogicalName() string
	NetworkInterfaceManaged() bool
	NetworkInterfacePrivateIPRef() string
	NetworkInterfacePrivateIPLogicalName() string
	ImportedAdvertisedFQDNRef() (string, error)
	LaunchConfigurationLogicalName() string
	LogicalName() string
	RecordSetManaged() bool
	RecordSetLogicalName() string
	SubnetRef() string
}

type etcdNodeImpl struct {
	cluster    EtcdCluster
	index      int
	config     model.EtcdNode
	subnet     model.Subnet
	natGateway model.NATGateway
}

func NewEtcdNodeDependsOnManagedNGW(cluster EtcdCluster, index int, nodeConfig model.EtcdNode, s model.Subnet, ngw model.NATGateway) EtcdNode {
	return etcdNodeImpl{
		cluster:    cluster,
		index:      index,
		config:     nodeConfig,
		subnet:     s,
		natGateway: ngw,
	}
}

func NewEtcdNode(cluster EtcdCluster, index int, nodeConfig model.EtcdNode, s model.Subnet) EtcdNode {
	return etcdNodeImpl{
		cluster: cluster,
		index:   index,
		config:  nodeConfig,
		subnet:  s,
	}
}

func (i etcdNodeImpl) Name() string {
	if i.config.Name != "" {
		return i.config.Name
	}
	return fmt.Sprintf("etcd%d", i.index)
}

func (i etcdNodeImpl) region() model.Region {
	return i.cluster.Region()
}

func (i etcdNodeImpl) customPrivateDNSName() string {
	if i.config.FQDN != "" {
		return i.config.FQDN
	}
	return fmt.Sprintf("%s.%s", i.Name(), i.cluster.InternalDomainName)
}

func (i etcdNodeImpl) privateDNSNameRef() string {
	if i.cluster.EC2InternalDomainUsed() {
		return i.defaultPrivateDNSNameRefFromIPRef(i.NetworkInterfacePrivateIPRef())
	}
	return fmt.Sprintf(`"%s"`, i.customPrivateDNSName())
}

func (i etcdNodeImpl) importedPrivateDNSNameRef() string {
	if i.cluster.EC2InternalDomainUsed() {
		return i.defaultPrivateDNSNameRefFromIPRef(fmt.Sprintf(`{ "Fn::ImportValue": {"Fn::Sub" : "${ControlPlaneStackName}-%s"} }`, i.NetworkInterfacePrivateIPLogicalName()))
	}
	return fmt.Sprintf(`"%s"`, i.customPrivateDNSName())
}

func (i etcdNodeImpl) defaultPrivateDNSNameRefFromIPRef(ipRef string) string {
	hostnameRef := fmt.Sprintf(`
	        { "Fn::Join" : [ "-",
	          [
                    "ip",
                    { "Fn::Join" : [ "-",
                      { "Fn::Split" : [ ".", %s ] }
                    ] }
                  ]
                ]}`, ipRef)
	return fmt.Sprintf(`{ "Fn::Join" : [ ".", [
                %s,
                "%s"
                ]]}`, hostnameRef, i.region().PrivateDomainName())
}

func (i etcdNodeImpl) defaultPublicDNSNameRef() (string, error) {
	eipRef, err := i.EIPRef()
	if err != nil {
		return "", fmt.Errorf("failed to determine an ec2 default public dns name: %v", err)
	}
	return i.defaultPublicDNSNameRefFromIPRef(eipRef), nil
}

func (i etcdNodeImpl) importedDefaultPublicDNSNameRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to determine an ec2 default public dns name: %v", err)
	}
	eipRef := fmt.Sprintf(`{ "Fn::ImportValue": {"Fn::Sub" : "${EtcdStackName}-%s"} }`, eipLogicalName)
	return i.defaultPublicDNSNameRefFromIPRef(eipRef), nil
}

func (i etcdNodeImpl) defaultPublicDNSNameRefFromIPRef(ipRef string) string {
	return fmt.Sprintf(`{ "Fn::Join" : [ ".", [
                { "Fn::Join" : [ "-", [
                "ec2",
                { "Fn::Join" : [ "-", { "Fn::Split" : [ ".", %s ] } ] }
                ]]},
                "%s"
                ]]}`, ipRef, i.region().PublicComputeDomainName())
}

func (i etcdNodeImpl) AdvertisedFQDNRef() (string, error) {
	if i.cluster.NodeShouldHaveSecondaryENI() {
		return i.privateDNSNameRef(), nil
	}
	return i.defaultPublicDNSNameRef()
}

func (i etcdNodeImpl) ImportedAdvertisedFQDNRef() (string, error) {
	if i.cluster.NodeShouldHaveSecondaryENI() {
		return i.importedPrivateDNSNameRef(), nil
	}
	return i.importedDefaultPublicDNSNameRef()
}

func (i etcdNodeImpl) SubnetRef() string {
	return i.subnet.Ref()
}

func (i etcdNodeImpl) SubnetAvailabilityZone() string {
	return i.subnet.AvailabilityZone
}

func (i etcdNodeImpl) DependencyExists() bool {
	return i.subnet.Private && i.subnet.ManageRouteToNATGateway()
}

func (i etcdNodeImpl) DependencyRef() (string, error) {
	// We have to wait until the route to the NAT gateway if it doesn't exist yet(hence ManageRoute=true) or the etcd node fails due to inability to connect internet
	if i.DependencyExists() {
		name := i.subnet.NATGatewayRouteLogicalName()
		return fmt.Sprintf(`"%s"`, name), nil
	}
	return "", nil
}

func (i etcdNodeImpl) EBSLogicalName() string {
	return fmt.Sprintf("Etcd%dEBS", i.index)
}

func (i etcdNodeImpl) EBSRef() string {
	return fmt.Sprintf(`{ "Ref" : "%s" }`, i.EBSLogicalName())
}

func (i etcdNodeImpl) EIPAllocationIDRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to derive the ref to the allocation id of an EIP: %v", err)
	}
	return fmt.Sprintf(`{ "Fn::GetAtt" : [ "%s", "AllocationId" ] }`, eipLogicalName), nil
}

func (i etcdNodeImpl) EIPLogicalName() (string, error) {
	if !i.EIPManaged() {
		return "", fmt.Errorf("[bug] EIPLogicalName invoked when EIP is not managed. Etcd node name: %s", i.Name())
	}
	return fmt.Sprintf("Etcd%dEIP", i.index), nil
}

func (i etcdNodeImpl) EIPManaged() bool {
	return i.cluster.NodeShouldHaveEIP()
}

func (i etcdNodeImpl) EIPRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to derive the ref to an EIP: %v", err)
	}
	return fmt.Sprintf(`{ "Ref" : "%s" }`, eipLogicalName), nil
}

func (i etcdNodeImpl) NetworkInterfaceIDRef() string {
	return fmt.Sprintf(`{ "Ref" : "%s" }`, i.NetworkInterfaceLogicalName())
}

func (i etcdNodeImpl) NetworkInterfaceLogicalName() string {
	return fmt.Sprintf("Etcd%dENI", i.index)
}

func (i etcdNodeImpl) NetworkInterfaceManaged() bool {
	return i.cluster.NodeShouldHaveSecondaryENI()
}

func (i etcdNodeImpl) NetworkInterfacePrivateIPRef() string {
	return fmt.Sprintf(`{ "Fn::GetAtt" : [ "%s", "PrimaryPrivateIpAddress" ] }`, i.NetworkInterfaceLogicalName())
}

// NetworkInterfacePrivateIPLogicalName returns the logical name of the launch configuration specific to this etcd node
func (i etcdNodeImpl) NetworkInterfacePrivateIPLogicalName() string {
	return fmt.Sprintf("%sPrivateIP", i.LogicalName())
}

// LaunchConfigurationLogicalName returns the logical name of the launch configuration specific to this etcd node
func (i etcdNodeImpl) LaunchConfigurationLogicalName() string {
	return fmt.Sprintf("%sLC", i.LogicalName())
}

func (i etcdNodeImpl) LogicalName() string {
	return fmt.Sprintf("Etcd%d", i.index)
}

func (i etcdNodeImpl) RecordSetManaged() bool {
	return i.cluster.NodeShouldHaveSecondaryENI() && i.cluster.RecordSetsManaged()
}

func (i etcdNodeImpl) RecordSetLogicalName() string {
	return fmt.Sprintf("Etcd%dInternalRecordSet", i.index)
}
<commit_msg>Use EtcdStackName for eni provider<commit_after>package derived

import (
	"fmt"
	"github.com/kubernetes-incubator/kube-aws/model"
)

type EtcdNode interface {
	AdvertisedFQDNRef() (string, error)
	DependencyExists() bool
	DependencyRef() (string, error)
	EBSLogicalName() string
	EBSRef() string
	EIPAllocationIDRef() (string, error)
	EIPLogicalName() (string, error)
	EIPManaged() bool
	EIPRef() (string, error)
	// The name of the etcd member runs on this etcd node
	Name() string
	NetworkInterfaceIDRef() string
	NetworkInterfaceLogicalName() string
	NetworkInterfaceManaged() bool
	NetworkInterfacePrivateIPRef() string
	NetworkInterfacePrivateIPLogicalName() string
	ImportedAdvertisedFQDNRef() (string, error)
	LaunchConfigurationLogicalName() string
	LogicalName() string
	RecordSetManaged() bool
	RecordSetLogicalName() string
	SubnetRef() string
}

type etcdNodeImpl struct {
	cluster    EtcdCluster
	index      int
	config     model.EtcdNode
	subnet     model.Subnet
	natGateway model.NATGateway
}

func NewEtcdNodeDependsOnManagedNGW(cluster EtcdCluster, index int, nodeConfig model.EtcdNode, s model.Subnet, ngw model.NATGateway) EtcdNode {
	return etcdNodeImpl{
		cluster:    cluster,
		index:      index,
		config:     nodeConfig,
		subnet:     s,
		natGateway: ngw,
	}
}

func NewEtcdNode(cluster EtcdCluster, index int, nodeConfig model.EtcdNode, s model.Subnet) EtcdNode {
	return etcdNodeImpl{
		cluster: cluster,
		index:   index,
		config:  nodeConfig,
		subnet:  s,
	}
}

func (i etcdNodeImpl) Name() string {
	if i.config.Name != "" {
		return i.config.Name
	}
	return fmt.Sprintf("etcd%d", i.index)
}

func (i etcdNodeImpl) region() model.Region {
	return i.cluster.Region()
}

func (i etcdNodeImpl) customPrivateDNSName() string {
	if i.config.FQDN != "" {
		return i.config.FQDN
	}
	return fmt.Sprintf("%s.%s", i.Name(), i.cluster.InternalDomainName)
}

func (i etcdNodeImpl) privateDNSNameRef() string {
	if i.cluster.EC2InternalDomainUsed() {
		return i.defaultPrivateDNSNameRefFromIPRef(i.NetworkInterfacePrivateIPRef())
	}
	return fmt.Sprintf(`"%s"`, i.customPrivateDNSName())
}

func (i etcdNodeImpl) importedPrivateDNSNameRef() string {
	if i.cluster.EC2InternalDomainUsed() {
		return i.defaultPrivateDNSNameRefFromIPRef(fmt.Sprintf(`{ "Fn::ImportValue": {"Fn::Sub" : "${EtcdStackName}-%s"} }`, i.NetworkInterfacePrivateIPLogicalName()))
	}
	return fmt.Sprintf(`"%s"`, i.customPrivateDNSName())
}

func (i etcdNodeImpl) defaultPrivateDNSNameRefFromIPRef(ipRef string) string {
	hostnameRef := fmt.Sprintf(`
	        { "Fn::Join" : [ "-",
	          [
                    "ip",
                    { "Fn::Join" : [ "-",
                      { "Fn::Split" : [ ".", %s ] }
                    ] }
                  ]
                ]}`, ipRef)
	return fmt.Sprintf(`{ "Fn::Join" : [ ".", [
                %s,
                "%s"
                ]]}`, hostnameRef, i.region().PrivateDomainName())
}

func (i etcdNodeImpl) defaultPublicDNSNameRef() (string, error) {
	eipRef, err := i.EIPRef()
	if err != nil {
		return "", fmt.Errorf("failed to determine an ec2 default public dns name: %v", err)
	}
	return i.defaultPublicDNSNameRefFromIPRef(eipRef), nil
}

func (i etcdNodeImpl) importedDefaultPublicDNSNameRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to determine an ec2 default public dns name: %v", err)
	}
	eipRef := fmt.Sprintf(`{ "Fn::ImportValue": {"Fn::Sub" : "${EtcdStackName}-%s"} }`, eipLogicalName)
	return i.defaultPublicDNSNameRefFromIPRef(eipRef), nil
}

func (i etcdNodeImpl) defaultPublicDNSNameRefFromIPRef(ipRef string) string {
	return fmt.Sprintf(`{ "Fn::Join" : [ ".", [
                { "Fn::Join" : [ "-", [
                "ec2",
                { "Fn::Join" : [ "-", { "Fn::Split" : [ ".", %s ] } ] }
                ]]},
                "%s"
                ]]}`, ipRef, i.region().PublicComputeDomainName())
}

func (i etcdNodeImpl) AdvertisedFQDNRef() (string, error) {
	if i.cluster.NodeShouldHaveSecondaryENI() {
		return i.privateDNSNameRef(), nil
	}
	return i.defaultPublicDNSNameRef()
}

func (i etcdNodeImpl) ImportedAdvertisedFQDNRef() (string, error) {
	if i.cluster.NodeShouldHaveSecondaryENI() {
		return i.importedPrivateDNSNameRef(), nil
	}
	return i.importedDefaultPublicDNSNameRef()
}

func (i etcdNodeImpl) SubnetRef() string {
	return i.subnet.Ref()
}

func (i etcdNodeImpl) SubnetAvailabilityZone() string {
	return i.subnet.AvailabilityZone
}

func (i etcdNodeImpl) DependencyExists() bool {
	return i.subnet.Private && i.subnet.ManageRouteToNATGateway()
}

func (i etcdNodeImpl) DependencyRef() (string, error) {
	// We have to wait until the route to the NAT gateway if it doesn't exist yet(hence ManageRoute=true) or the etcd node fails due to inability to connect internet
	if i.DependencyExists() {
		name := i.subnet.NATGatewayRouteLogicalName()
		return fmt.Sprintf(`"%s"`, name), nil
	}
	return "", nil
}

func (i etcdNodeImpl) EBSLogicalName() string {
	return fmt.Sprintf("Etcd%dEBS", i.index)
}

func (i etcdNodeImpl) EBSRef() string {
	return fmt.Sprintf(`{ "Ref" : "%s" }`, i.EBSLogicalName())
}

func (i etcdNodeImpl) EIPAllocationIDRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to derive the ref to the allocation id of an EIP: %v", err)
	}
	return fmt.Sprintf(`{ "Fn::GetAtt" : [ "%s", "AllocationId" ] }`, eipLogicalName), nil
}

func (i etcdNodeImpl) EIPLogicalName() (string, error) {
	if !i.EIPManaged() {
		return "", fmt.Errorf("[bug] EIPLogicalName invoked when EIP is not managed. Etcd node name: %s", i.Name())
	}
	return fmt.Sprintf("Etcd%dEIP", i.index), nil
}

func (i etcdNodeImpl) EIPManaged() bool {
	return i.cluster.NodeShouldHaveEIP()
}

func (i etcdNodeImpl) EIPRef() (string, error) {
	eipLogicalName, err := i.EIPLogicalName()
	if err != nil {
		return "", fmt.Errorf("failed to derive the ref to an EIP: %v", err)
	}
	return fmt.Sprintf(`{ "Ref" : "%s" }`, eipLogicalName), nil
}

func (i etcdNodeImpl) NetworkInterfaceIDRef() string {
	return fmt.Sprintf(`{ "Ref" : "%s" }`, i.NetworkInterfaceLogicalName())
}

func (i etcdNodeImpl) NetworkInterfaceLogicalName() string {
	return fmt.Sprintf("Etcd%dENI", i.index)
}

func (i etcdNodeImpl) NetworkInterfaceManaged() bool {
	return i.cluster.NodeShouldHaveSecondaryENI()
}

func (i etcdNodeImpl) NetworkInterfacePrivateIPRef() string {
	return fmt.Sprintf(`{ "Fn::GetAtt" : [ "%s", "PrimaryPrivateIpAddress" ] }`, i.NetworkInterfaceLogicalName())
}

// NetworkInterfacePrivateIPLogicalName returns the logical name of the launch configuration specific to this etcd node
func (i etcdNodeImpl) NetworkInterfacePrivateIPLogicalName() string {
	return fmt.Sprintf("%sPrivateIP", i.LogicalName())
}

// LaunchConfigurationLogicalName returns the logical name of the launch configuration specific to this etcd node
func (i etcdNodeImpl) LaunchConfigurationLogicalName() string {
	return fmt.Sprintf("%sLC", i.LogicalName())
}

func (i etcdNodeImpl) LogicalName() string {
	return fmt.Sprintf("Etcd%d", i.index)
}

func (i etcdNodeImpl) RecordSetManaged() bool {
	return i.cluster.NodeShouldHaveSecondaryENI() && i.cluster.RecordSetsManaged()
}

func (i etcdNodeImpl) RecordSetLogicalName() string {
	return fmt.Sprintf("Etcd%dInternalRecordSet", i.index)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Fission 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 router

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"

	"github.com/gorilla/mux"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/fission/fission"
	executorClient "github.com/fission/fission/executor/client"
)

type functionHandler struct {
	fmap     *functionServiceMap
	executor *executorClient.Client
	function *metav1.ObjectMeta
}

// A layer on top of http.DefaultTransport, with retries.
type RetryingRoundTripper struct {
	maxRetries     int
	initialTimeout time.Duration
	funcHandler    *functionHandler
}

// RoundTrip is a custom transport with retries for http requests that forwards the request to the right serviceUrl, obtained
// from router's cache or from executor if router entry is stale.
//
// It first checks if the service address for this function came from router's cache.
// If it didn't, it makes a request to executor to get a new service for function. If that succeeds, it adds the address
// to it's cache and makes a request to that address with transport.RoundTrip call.
// Initial requests to new k8s services sometimes seem to fail, but retries work. So, it retries with an exponential
// back-off for maxRetries times.
//
// Else if it came from the cache, it makes a transport.RoundTrip with that cached address. If the response received is
// a network dial error (which means that the pod doesn't exist anymore), it removes the cache entry and makes a request
// to executor to get a new service for function. It then retries transport.RoundTrip with the new address.
//
// At any point in time, if the response received from transport.RoundTrip is other than dial network error, it is
// relayed as-is to the user, without any retries.
//
// While this RoundTripper handles the case where a previously cached address of the function pod isn't valid anymore
// (probably because the pod got deleted somehow), by making a request to executor to get a new service for this function,
// it doesn't handle a case where a newly specialized pod gets deleted just after the GetServiceForFunction succeeds.
// In such a case, the RoundTripper will retry requests against the new address and give up after maxRetries.
// However, the subsequent http call for this function will ensure the cache is invalidated.
//
// If GetServiceForFunction returns an error or if RoundTripper exits with an error, it get's translated into 502
// inside ServeHttp function of the reverseProxy.
// Earlier, GetServiceForFunction was called inside handler function and fission explicitly set http status code to 500
// if it returned an error.
func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	var needExecutor, serviceUrlFromExecutor bool
	var serviceUrl *url.URL

	// set the timeout for transport context
	timeout := roundTripper.initialTimeout
	transport := http.DefaultTransport.(*http.Transport)
	defer transport.CloseIdleConnections()

	// cache lookup to get serviceUrl
	serviceUrl, err = roundTripper.funcHandler.fmap.lookup(roundTripper.funcHandler.function)
	if err != nil || serviceUrl == nil {
		// cache miss or nil entry in cache
		needExecutor = true
	}

	for i := 0; i < roundTripper.maxRetries-1; i++ {
		if needExecutor {
			log.Printf("Calling getServiceForFunction for function: %s", roundTripper.funcHandler.function.Name)

			// send a request to executor to specialize a new pod
			service, err := roundTripper.funcHandler.executor.GetServiceForFunction(
				roundTripper.funcHandler.function)
			if err != nil {
				// We might want a specific error code or header for fission failures as opposed to
				// user function bugs.
				return nil, err
			}

			// parse the address into url
			serviceUrl, err = url.Parse(fmt.Sprintf("http://%v", service))
			if err != nil {
				return nil, err
			}

			// add the address in router's cache
			roundTripper.funcHandler.fmap.assign(roundTripper.funcHandler.function, serviceUrl)

			// flag denotes that service was not obtained from cache, instead, created just now by executor
			serviceUrlFromExecutor = true
		}

		// modify the request to reflect the service url
		// this service url may have come from the cache lookup or from executor response
		req.URL.Scheme = serviceUrl.Scheme
		req.URL.Host = serviceUrl.Host

		// To keep the function run container simple, it
		// doesn't do any routing.  In the future if we have
		// multiple functions per container, we could use the
		// function metadata here.
		// leave the query string intact (req.URL.RawQuery)
		req.URL.Path = "/"

		// Overwrite request host with internal host,
		// or request will be blocked in some situations
		// (e.g. istio-proxy)
		req.Host = serviceUrl.Host

		// over-riding default settings.
		transport.DialContext = (&net.Dialer{
			Timeout:   timeout,
			KeepAlive: 30 * time.Second,
		}).DialContext

		// forward the request to the function service
		resp, err = transport.RoundTrip(req)
		if err == nil {
			// if transport.RoundTrip succeeds and it was a cached entry, then tapService
			if !serviceUrlFromExecutor {
				go roundTripper.funcHandler.tapService(serviceUrl)
			}
			// return response back to user
			return resp, nil
		}

		// if transport.RoundTrip returns a non-network dial error, then relay it back to user
		if !fission.IsNetworkDialError(err) {
			return resp, err
		}

		// means its a newly created service and it returned a network dial error.
		// just retry after backing off for timeout period.
		if serviceUrlFromExecutor {
			log.Printf("request to %s errored out. backing off for %v before retrying",
				req.URL.Host, timeout)
			timeout *= time.Duration(2)
			time.Sleep(timeout)
			needExecutor = false
			continue
		} else {
			// if transport.RoundTrip returns a network dial error and serviceUrl was from cache,
			// it means, the entry in router cache is stale, so invalidate it.
			// also set needExecutor to true so a new service can be requested for function.
			log.Printf("request to %s errored out. removing function : %s from router's cache "+
				"and requesting a new service for function",
				req.URL.Host, roundTripper.funcHandler.function.Name)
			roundTripper.funcHandler.fmap.remove(roundTripper.funcHandler.function)
			needExecutor = true
		}
	}

	// finally, one more retry with the default timeout
	return http.DefaultTransport.RoundTrip(req)
}

func (fh *functionHandler) tapService(serviceUrl *url.URL) {
	if fh.executor == nil {
		return
	}
	fh.executor.TapService(serviceUrl)
}

func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) {
	// retrieve url params and add them to request header
	vars := mux.Vars(request)
	for k, v := range vars {
		request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v)
	}

	// System Params
	MetadataToHeaders(HEADERS_FISSION_FUNCTION_PREFIX, fh.function, request)

	// TODO: As an optimization we may want to cache proxies too -- this might get us
	// connection reuse and possibly better performance
	director := func(req *http.Request) {
		if _, ok := req.Header["User-Agent"]; !ok {
			// explicitly disable User-Agent so it's not set to default value
			req.Header.Set("User-Agent", "")
		}
	}

	proxy := &httputil.ReverseProxy{
		Director: director,
		Transport: &RetryingRoundTripper{
			initialTimeout: 50 * time.Millisecond,
			maxRetries:     10,
			funcHandler:    fh,
		},
	}

	proxy.ServeHTTP(responseWriter, request)
}
<commit_msg>Prevent releasing idle connections because transport is shared. (#609)<commit_after>/*
Copyright 2016 The Fission 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 router

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"

	"github.com/gorilla/mux"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/fission/fission"
	executorClient "github.com/fission/fission/executor/client"
)

type functionHandler struct {
	fmap     *functionServiceMap
	executor *executorClient.Client
	function *metav1.ObjectMeta
}

// A layer on top of http.DefaultTransport, with retries.
type RetryingRoundTripper struct {
	maxRetries     int
	initialTimeout time.Duration
	funcHandler    *functionHandler
}

// RoundTrip is a custom transport with retries for http requests that forwards the request to the right serviceUrl, obtained
// from router's cache or from executor if router entry is stale.
//
// It first checks if the service address for this function came from router's cache.
// If it didn't, it makes a request to executor to get a new service for function. If that succeeds, it adds the address
// to it's cache and makes a request to that address with transport.RoundTrip call.
// Initial requests to new k8s services sometimes seem to fail, but retries work. So, it retries with an exponential
// back-off for maxRetries times.
//
// Else if it came from the cache, it makes a transport.RoundTrip with that cached address. If the response received is
// a network dial error (which means that the pod doesn't exist anymore), it removes the cache entry and makes a request
// to executor to get a new service for function. It then retries transport.RoundTrip with the new address.
//
// At any point in time, if the response received from transport.RoundTrip is other than dial network error, it is
// relayed as-is to the user, without any retries.
//
// While this RoundTripper handles the case where a previously cached address of the function pod isn't valid anymore
// (probably because the pod got deleted somehow), by making a request to executor to get a new service for this function,
// it doesn't handle a case where a newly specialized pod gets deleted just after the GetServiceForFunction succeeds.
// In such a case, the RoundTripper will retry requests against the new address and give up after maxRetries.
// However, the subsequent http call for this function will ensure the cache is invalidated.
//
// If GetServiceForFunction returns an error or if RoundTripper exits with an error, it get's translated into 502
// inside ServeHttp function of the reverseProxy.
// Earlier, GetServiceForFunction was called inside handler function and fission explicitly set http status code to 500
// if it returned an error.
func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	var needExecutor, serviceUrlFromExecutor bool
	var serviceUrl *url.URL

	// set the timeout for transport context
	timeout := roundTripper.initialTimeout
	transport := http.DefaultTransport.(*http.Transport)

	// cache lookup to get serviceUrl
	serviceUrl, err = roundTripper.funcHandler.fmap.lookup(roundTripper.funcHandler.function)
	if err != nil || serviceUrl == nil {
		// cache miss or nil entry in cache
		needExecutor = true
	}

	for i := 0; i < roundTripper.maxRetries-1; i++ {
		if needExecutor {
			log.Printf("Calling getServiceForFunction for function: %s", roundTripper.funcHandler.function.Name)

			// send a request to executor to specialize a new pod
			service, err := roundTripper.funcHandler.executor.GetServiceForFunction(
				roundTripper.funcHandler.function)
			if err != nil {
				// We might want a specific error code or header for fission failures as opposed to
				// user function bugs.
				return nil, err
			}

			// parse the address into url
			serviceUrl, err = url.Parse(fmt.Sprintf("http://%v", service))
			if err != nil {
				return nil, err
			}

			// add the address in router's cache
			roundTripper.funcHandler.fmap.assign(roundTripper.funcHandler.function, serviceUrl)

			// flag denotes that service was not obtained from cache, instead, created just now by executor
			serviceUrlFromExecutor = true
		}

		// modify the request to reflect the service url
		// this service url may have come from the cache lookup or from executor response
		req.URL.Scheme = serviceUrl.Scheme
		req.URL.Host = serviceUrl.Host

		// To keep the function run container simple, it
		// doesn't do any routing.  In the future if we have
		// multiple functions per container, we could use the
		// function metadata here.
		// leave the query string intact (req.URL.RawQuery)
		req.URL.Path = "/"

		// Overwrite request host with internal host,
		// or request will be blocked in some situations
		// (e.g. istio-proxy)
		req.Host = serviceUrl.Host

		// over-riding default settings.
		transport.DialContext = (&net.Dialer{
			Timeout:   timeout,
			KeepAlive: 30 * time.Second,
		}).DialContext

		// forward the request to the function service
		resp, err = transport.RoundTrip(req)
		if err == nil {
			// if transport.RoundTrip succeeds and it was a cached entry, then tapService
			if !serviceUrlFromExecutor {
				go roundTripper.funcHandler.tapService(serviceUrl)
			}
			// return response back to user
			return resp, nil
		}

		// if transport.RoundTrip returns a non-network dial error, then relay it back to user
		if !fission.IsNetworkDialError(err) {
			return resp, err
		}

		// means its a newly created service and it returned a network dial error.
		// just retry after backing off for timeout period.
		if serviceUrlFromExecutor {
			log.Printf("request to %s errored out. backing off for %v before retrying",
				req.URL.Host, timeout)
			timeout *= time.Duration(2)
			time.Sleep(timeout)
			needExecutor = false
			continue
		} else {
			// if transport.RoundTrip returns a network dial error and serviceUrl was from cache,
			// it means, the entry in router cache is stale, so invalidate it.
			// also set needExecutor to true so a new service can be requested for function.
			log.Printf("request to %s errored out. removing function : %s from router's cache "+
				"and requesting a new service for function",
				req.URL.Host, roundTripper.funcHandler.function.Name)
			roundTripper.funcHandler.fmap.remove(roundTripper.funcHandler.function)
			needExecutor = true
		}
	}

	// finally, one more retry with the default timeout
	return http.DefaultTransport.RoundTrip(req)
}

func (fh *functionHandler) tapService(serviceUrl *url.URL) {
	if fh.executor == nil {
		return
	}
	fh.executor.TapService(serviceUrl)
}

func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) {
	// retrieve url params and add them to request header
	vars := mux.Vars(request)
	for k, v := range vars {
		request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v)
	}

	// System Params
	MetadataToHeaders(HEADERS_FISSION_FUNCTION_PREFIX, fh.function, request)

	// TODO: As an optimization we may want to cache proxies too -- this might get us
	// connection reuse and possibly better performance
	director := func(req *http.Request) {
		if _, ok := req.Header["User-Agent"]; !ok {
			// explicitly disable User-Agent so it's not set to default value
			req.Header.Set("User-Agent", "")
		}
	}

	proxy := &httputil.ReverseProxy{
		Director: director,
		Transport: &RetryingRoundTripper{
			initialTimeout: 50 * time.Millisecond,
			maxRetries:     10,
			funcHandler:    fh,
		},
	}

	proxy.ServeHTTP(responseWriter, request)
}
<|endoftext|>