<commit_before>package manager

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/dockpit/go-dockerclient"

	"github.com/dockpit/dirtar"
)

type Manager struct {
	client *docker.Client
	host   string
}

// the Docker image that is used for mocks
var ImageName = "dockpit/mock:latest"
var MockPrivatePort int64 = 8000
var ReadyExp = regexp.MustCompile(".*serving on.*")
var ReadyInterval = time.Millisecond * 50
var ReadyTimeout = time.Second * 1

// Manages state for microservice testing by creating
// docker images and starting containers when necessary
func NewManager(host, cert string) (*Manager, error) {
	m := &Manager{
		host: host,
	}

	//parse docker host addr as url
	hurl, err := url.Parse(host)
	if err != nil {
		return nil, err
	}

	//change to http connection
	hurl.Scheme = "https"

	//create docker client
	m.client, err = docker.NewTLSClient(hurl.String(), filepath.Join(cert, "cert.pem"), filepath.Join(cert, "key.pem"), filepath.Join(cert, "ca.pem"))
	if err != nil {
		return nil, err
	}

	return m, nil
}

// hash a path using md5
func containerName(path string) (string, error) {

	//create md5 of full path
	hash := md5.New()
	_, err := hash.Write([]byte(path))
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("pitmock_%s", hex.EncodeToString(hash.Sum(nil))), nil
}

// start a mock container by using examples from the given directory
func (m *Manager) Start(dir string, portb map[docker.Port][]docker.PortBinding) (*MockContainer, error) {

	//create name for container
	cname, err := containerName(dir)
	if err != nil {
		return nil, err
	}

	//@todo, grab the first and set private port to expected mock service
	//to the mock porcess binding
	for _, pb := range portb {
		portb[docker.Port(strconv.FormatInt(MockPrivatePort, 10)+"/tcp")] = pb
		break
	}

	//create the container
	c, err := m.client.CreateContainer(docker.CreateContainerOptions{
		Name: cname,
		Config: &docker.Config{
			Image: ImageName,
		},
	})

	if err != nil {
		return nil, err
	}

	//start the container we created
	err = m.client.StartContainer(c.ID, &docker.HostConfig{PortBindings: portb})
	if err != nil {
		return nil, err
	}

	//wait for mock to be ready

	//get container port mapping
	ci, err := m.client.InspectContainer(c.ID)
	if err != nil {
		return nil, err
	}

	//use docker host location to form url
	hurl, err := url.Parse(m.host)
	if err != nil {
		return nil, err
	}

	//tar examples into memory
	tar := bytes.NewBuffer(nil)
	err = dirtar.Tar(dir, tar)
	if err != nil {
		return nil, err
	}

	//'ping' logs until we got something that indicates
	// it started
	to := make(chan bool, 1)
	go func() {
		time.Sleep(ReadyTimeout)
		to <- true
	}()

	//start pinging logs
	var buf bytes.Buffer
	for {

		buf.Reset()
		err = m.client.Logs(docker.LogsOptions{
			Container:    c.ID,
			OutputStream: &buf,
			ErrorStream:  &buf,
			Stdout:       true,
			Stderr:       true,
			RawTerminal:  true,
		})
		if err != nil {
			return nil, err
		}

		//if it matches; break loop we can continue
		if ReadyExp.MatchString(buf.String()) {
			break
		}

		select {
		case <-to:
			return nil, fmt.Errorf("Mock server starting timed out")
			break
		case <-time.After(ReadyInterval):
			continue
		}

	}

	//get the external port for 8000 and turn into an url we can send http requests to
	//@todo, here we assume that the first configured port is the http interface, indicate explicetly
	//@todo, use the logic on line 77
	hurl.Scheme = "http"
	for _, pconfig := range ci.NetworkSettings.PortMappingAPI() {
		if pconfig.PrivatePort == MockPrivatePort {
			hurl.Host = strings.Replace(hurl.Host, ":2376", fmt.Sprintf(":%d", pconfig.PublicPort), 1)
		}
	}

	//send the upload request
	resp, err := http.Post(fmt.Sprintf("%s/_examples", hurl.String()), "application/x-tar", tar)
	if err != nil {
		return nil, err
	}

	//check if we uploaded correctly
	if resp.StatusCode != 201 {
		return nil, fmt.Errorf("Failed to upload examples to container '%s' %s", ci.ID, ci.Name)
	}

	//send HUP signal to 'root' process (dockpit/mock) to reload examples
	err = m.client.KillContainer(docker.KillContainerOptions{
		ID:     c.ID,
		Signal: docker.Signal(syscall.SIGHUP),
	})

	if err != nil {
		return nil, err
	}

	return &MockContainer{c.ID, hurl.String(), dir}, nil
}

// stop a mock container that was started from the given directory
func (m *Manager) Stop(dir string) error {

	//create name for container
	cname, err := containerName(dir)
	if err != nil {
		return err
	}

	//get all containers
	cs, err := m.client.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		return err
	}

	//get container that matches the name
	// var container *docker.APIContainers
	var container docker.APIContainers
	for _, c := range cs {
		for _, n := range c.Names {
			if n[1:] == cname {
				container = c
			}
		}
	}

	//remove hard since mocks are ephemeral
	return m.client.RemoveContainer(docker.RemoveContainerOptions{
		ID:            container.ID,
		RemoveVolumes: true,
		Force:         true,
	})
}
<commit_msg>removed some comments<commit_after>package manager

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/dockpit/go-dockerclient"

	"github.com/dockpit/dirtar"
)

type Manager struct {
	client *docker.Client
	host   string
}

// the Docker image that is used for mocks
var ImageName = "dockpit/mock:latest"
var MockPrivatePort int64 = 8000
var ReadyExp = regexp.MustCompile(".*serving on.*")
var ReadyInterval = time.Millisecond * 50
var ReadyTimeout = time.Second * 1

// Manages state for microservice testing by creating
// docker images and starting containers when necessary
func NewManager(host, cert string) (*Manager, error) {
	m := &Manager{
		host: host,
	}

	//parse docker host addr as url
	hurl, err := url.Parse(host)
	if err != nil {
		return nil, err
	}

	//change to http connection
	hurl.Scheme = "https"

	//create docker client
	m.client, err = docker.NewTLSClient(hurl.String(), filepath.Join(cert, "cert.pem"), filepath.Join(cert, "key.pem"), filepath.Join(cert, "ca.pem"))
	if err != nil {
		return nil, err
	}

	return m, nil
}

// hash a path using md5
func containerName(path string) (string, error) {

	//create md5 of full path
	hash := md5.New()
	_, err := hash.Write([]byte(path))
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("pitmock_%s", hex.EncodeToString(hash.Sum(nil))), nil
}

// start a mock container by using examples from the given directory
func (m *Manager) Start(dir string, portb map[docker.Port][]docker.PortBinding) (*MockContainer, error) {

	//create name for container
	cname, err := containerName(dir)
	if err != nil {
		return nil, err
	}

	//@todo, grab the first and set private port to expected mock service
	//to the mock porcess binding
	for _, pb := range portb {
		portb[docker.Port(strconv.FormatInt(MockPrivatePort, 10)+"/tcp")] = pb
		break
	}

	//create the container
	c, err := m.client.CreateContainer(docker.CreateContainerOptions{
		Name: cname,
		Config: &docker.Config{
			Image: ImageName,
		},
	})

	if err != nil {
		return nil, err
	}

	//start the container we created
	err = m.client.StartContainer(c.ID, &docker.HostConfig{PortBindings: portb})
	if err != nil {
		return nil, err
	}

	//get container port mapping
	ci, err := m.client.InspectContainer(c.ID)
	if err != nil {
		return nil, err
	}

	//use docker host location to form url
	hurl, err := url.Parse(m.host)
	if err != nil {
		return nil, err
	}

	//tar examples into memory
	tar := bytes.NewBuffer(nil)
	err = dirtar.Tar(dir, tar)
	if err != nil {
		return nil, err
	}

	//'ping' logs until we got something that indicates
	// it started
	to := make(chan bool, 1)
	go func() {
		time.Sleep(ReadyTimeout)
		to <- true
	}()

	//start pinging logs
	var buf bytes.Buffer
	for {

		buf.Reset()
		err = m.client.Logs(docker.LogsOptions{
			Container:    c.ID,
			OutputStream: &buf,
			ErrorStream:  &buf,
			Stdout:       true,
			Stderr:       true,
			RawTerminal:  true,
		})
		if err != nil {
			return nil, err
		}

		//if it matches; break loop we can continue
		if ReadyExp.MatchString(buf.String()) {
			break
		}

		select {
		case <-to:
			return nil, fmt.Errorf("Mock server starting timed out")
			break
		case <-time.After(ReadyInterval):
			continue
		}

	}

	//get the external port for 8000 and turn into an url we can send http requests to
	//@todo, here we assume that the first configured port is the http interface, indicate explicetly
	//@todo, use the logic on line 77
	hurl.Scheme = "http"
	for _, pconfig := range ci.NetworkSettings.PortMappingAPI() {
		if pconfig.PrivatePort == MockPrivatePort {
			hurl.Host = strings.Replace(hurl.Host, ":2376", fmt.Sprintf(":%d", pconfig.PublicPort), 1)
		}
	}

	//send the upload request
	resp, err := http.Post(fmt.Sprintf("%s/_examples", hurl.String()), "application/x-tar", tar)
	if err != nil {
		return nil, err
	}

	//check if we uploaded correctly
	if resp.StatusCode != 201 {
		return nil, fmt.Errorf("Failed to upload examples to container '%s' %s", ci.ID, ci.Name)
	}

	//send HUP signal to 'root' process (dockpit/mock) to reload examples
	err = m.client.KillContainer(docker.KillContainerOptions{
		ID:     c.ID,
		Signal: docker.Signal(syscall.SIGHUP),
	})

	if err != nil {
		return nil, err
	}

	return &MockContainer{c.ID, hurl.String(), dir}, nil
}

// stop a mock container that was started from the given directory
func (m *Manager) Stop(dir string) error {

	//create name for container
	cname, err := containerName(dir)
	if err != nil {
		return err
	}

	//get all containers
	cs, err := m.client.ListContainers(docker.ListContainersOptions{})
	if err != nil {
		return err
	}

	//get container that matches the name
	// var container *docker.APIContainers
	var container docker.APIContainers
	for _, c := range cs {
		for _, n := range c.Names {
			if n[1:] == cname {
				container = c
			}
		}
	}

	//remove hard since mocks are ephemeral
	return m.client.RemoveContainer(docker.RemoveContainerOptions{
		ID:            container.ID,
		RemoveVolumes: true,
		Force:         true,
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 client

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	"k8s.io/klog/v2"
	"sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client"
)

// Tunnel provides ability to dial a connection through a tunnel.
type Tunnel interface {
	// Dial connects to the address on the named network, similar to
	// what net.Dial does. The only supported protocol is tcp.
	DialContext(ctx context.Context, protocol, address string) (net.Conn, error)
}

type dialResult struct {
	err    string
	connid int64
}

// grpcTunnel implements Tunnel
type grpcTunnel struct {
	stream          client.ProxyService_ProxyClient
	pendingDial     map[int64]chan<- dialResult
	conns           map[int64]*conn
	pendingDialLock sync.RWMutex
	connsLock       sync.RWMutex

	// The tunnel will be closed if the caller fails to read via conn.Read()
	// more than readTimeoutSeconds after a packet has been received.
	readTimeoutSeconds int
}

type clientConn interface {
	Close() error
}

var _ clientConn = &grpc.ClientConn{}

// CreateSingleUseGrpcTunnel creates a Tunnel to dial to a remote server through a
// gRPC based proxy service.
// Currently, a single tunnel supports a single connection, and the tunnel is closed when the connection is terminated
// The Dial() method of the returned tunnel should only be called once
func CreateSingleUseGrpcTunnel(ctx context.Context, address string, opts ...grpc.DialOption) (Tunnel, error) {
	c, err := grpc.DialContext(ctx, address, opts...)
	if err != nil {
		return nil, err
	}

	grpcClient := client.NewProxyServiceClient(c)

	stream, err := grpcClient.Proxy(ctx)
	if err != nil {
		c.Close()
		return nil, err
	}

	tunnel := &grpcTunnel{
		stream:             stream,
		pendingDial:        make(map[int64]chan<- dialResult),
		conns:              make(map[int64]*conn),
		readTimeoutSeconds: 10,
	}

	go tunnel.serve(c)

	return tunnel, nil
}

func (t *grpcTunnel) serve(c clientConn) {
	defer c.Close()

	for {
		pkt, err := t.stream.Recv()
		if err == io.EOF {
			return
		}
		if err != nil || pkt == nil {
			klog.ErrorS(err, "stream read failure")
			return
		}

		klog.V(5).InfoS("[tracing] recv packet", "type", pkt.Type)

		switch pkt.Type {
		case client.PacketType_DIAL_RSP:
			resp := pkt.GetDialResponse()
			t.pendingDialLock.RLock()
			ch, ok := t.pendingDial[resp.Random]
			t.pendingDialLock.RUnlock()

			if !ok {
				klog.V(1).InfoS("DialResp not recognized; dropped", "connectionID", resp.ConnectID, "dialID", resp.Random)
				return
			} else {
				result := dialResult{
					err:    resp.Error,
					connid: resp.ConnectID,
				}
				select {
				case ch <- result:
				default:
					// If there are no readers of the pending dial channel above, it means one of two things:
					//   1. There was a second DIAL_RSP for the connection request (this is very unlikely but possible)
					//   2. grpcTunnel.DialContext() returned early due to a dial timeout or the client canceling the context
					//
					// In either scenario, we should return here as this tunnel is no longer needed.
					klog.V(1).InfoS("DialResp has no receiver; dropped", "connectionID", resp.ConnectID, "dialID", resp.Random)
					return
				}
			}

			if resp.Error != "" {
				// On dial error, avoid leaking serve goroutine.
				return
			}

		case client.PacketType_DATA:
			resp := pkt.GetData()
			// TODO: flow control
			t.connsLock.RLock()
			conn, ok := t.conns[resp.ConnectID]
			t.connsLock.RUnlock()

			if ok {
				timer := time.NewTimer((time.Duration)(t.readTimeoutSeconds) * time.Second)
				select {
				case conn.readCh <- resp.Data:
					timer.Stop()
				case <-timer.C:
					klog.ErrorS(fmt.Errorf("timeout"), "readTimeout has been reached, the grpc connection to the proxy server will be closed", "connectionID", conn.connID, "readTimeoutSeconds", t.readTimeoutSeconds)
					return
				}
			} else {
				klog.V(1).InfoS("connection not recognized", "connectionID", resp.ConnectID)
			}
		case client.PacketType_CLOSE_RSP:
			resp := pkt.GetCloseResponse()
			t.connsLock.RLock()
			conn, ok := t.conns[resp.ConnectID]
			t.connsLock.RUnlock()

			if ok {
				close(conn.readCh)
				conn.closeCh <- resp.Error
				close(conn.closeCh)
				t.connsLock.Lock()
				delete(t.conns, resp.ConnectID)
				t.connsLock.Unlock()
				return
			}
			klog.V(1).InfoS("connection not recognized", "connectionID", resp.ConnectID)
		}
	}
}

// Dial connects to the address on the named network, similar to
// what net.Dial does. The only supported protocol is tcp.
func (t *grpcTunnel) DialContext(ctx context.Context, protocol, address string) (net.Conn, error) {
	if protocol != "tcp" {
		return nil, errors.New("protocol not supported")
	}

	random := rand.Int63() /* #nosec G404 */
	// This channel MUST NOT be buffered. The sender needs to know when we are not receiving things, so they can abort.
	resCh := make(chan dialResult)
	t.pendingDialLock.Lock()
	t.pendingDial[random] = resCh
	t.pendingDialLock.Unlock()
	defer func() {
		t.pendingDialLock.Lock()
		delete(t.pendingDial, random)
		t.pendingDialLock.Unlock()
	}()

	req := &client.Packet{
		Type: client.PacketType_DIAL_REQ,
		Payload: &client.Packet_DialRequest{
			DialRequest: &client.DialRequest{
				Protocol: protocol,
				Address:  address,
				Random:   random,
			},
		},
	}
	klog.V(5).InfoS("[tracing] send packet", "type", req.Type)

	err := t.stream.Send(req)
	if err != nil {
		return nil, err
	}

	klog.V(5).Infoln("DIAL_REQ sent to proxy server")

	c := &conn{stream: t.stream, random: random}

	select {
	case res := <-resCh:
		if res.err != "" {
			return nil, errors.New(res.err)
		}
		c.connID = res.connid
		c.readCh = make(chan []byte, 10)
		c.closeCh = make(chan string, 1)
		t.connsLock.Lock()
		t.conns[res.connid] = c
		t.connsLock.Unlock()
	case <-time.After(30 * time.Second):
		return nil, errors.New("dial timeout, backstop")
	case <-ctx.Done():
		return nil, errors.New("dial timeout, context")
	}

	return c, nil
}
<commit_msg>konnectivity-client: add v=5 logs for timeout/context cancel waiting for DialResp<commit_after>/*
Copyright 2019 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 client

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	"k8s.io/klog/v2"
	"sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client"
)

// Tunnel provides ability to dial a connection through a tunnel.
type Tunnel interface {
	// Dial connects to the address on the named network, similar to
	// what net.Dial does. The only supported protocol is tcp.
	DialContext(ctx context.Context, protocol, address string) (net.Conn, error)
}

type dialResult struct {
	err    string
	connid int64
}

// grpcTunnel implements Tunnel
type grpcTunnel struct {
	stream          client.ProxyService_ProxyClient
	pendingDial     map[int64]chan<- dialResult
	conns           map[int64]*conn
	pendingDialLock sync.RWMutex
	connsLock       sync.RWMutex

	// The tunnel will be closed if the caller fails to read via conn.Read()
	// more than readTimeoutSeconds after a packet has been received.
	readTimeoutSeconds int
}

type clientConn interface {
	Close() error
}

var _ clientConn = &grpc.ClientConn{}

// CreateSingleUseGrpcTunnel creates a Tunnel to dial to a remote server through a
// gRPC based proxy service.
// Currently, a single tunnel supports a single connection, and the tunnel is closed when the connection is terminated
// The Dial() method of the returned tunnel should only be called once
func CreateSingleUseGrpcTunnel(ctx context.Context, address string, opts ...grpc.DialOption) (Tunnel, error) {
	c, err := grpc.DialContext(ctx, address, opts...)
	if err != nil {
		return nil, err
	}

	grpcClient := client.NewProxyServiceClient(c)

	stream, err := grpcClient.Proxy(ctx)
	if err != nil {
		c.Close()
		return nil, err
	}

	tunnel := &grpcTunnel{
		stream:             stream,
		pendingDial:        make(map[int64]chan<- dialResult),
		conns:              make(map[int64]*conn),
		readTimeoutSeconds: 10,
	}

	go tunnel.serve(c)

	return tunnel, nil
}

func (t *grpcTunnel) serve(c clientConn) {
	defer c.Close()

	for {
		pkt, err := t.stream.Recv()
		if err == io.EOF {
			return
		}
		if err != nil || pkt == nil {
			klog.ErrorS(err, "stream read failure")
			return
		}

		klog.V(5).InfoS("[tracing] recv packet", "type", pkt.Type)

		switch pkt.Type {
		case client.PacketType_DIAL_RSP:
			resp := pkt.GetDialResponse()
			t.pendingDialLock.RLock()
			ch, ok := t.pendingDial[resp.Random]
			t.pendingDialLock.RUnlock()

			if !ok {
				klog.V(1).InfoS("DialResp not recognized; dropped", "connectionID", resp.ConnectID, "dialID", resp.Random)
				return
			} else {
				result := dialResult{
					err:    resp.Error,
					connid: resp.ConnectID,
				}
				select {
				case ch <- result:
				default:
					// If there are no readers of the pending dial channel above, it means one of two things:
					//   1. There was a second DIAL_RSP for the connection request (this is very unlikely but possible)
					//   2. grpcTunnel.DialContext() returned early due to a dial timeout or the client canceling the context
					//
					// In either scenario, we should return here as this tunnel is no longer needed.
					klog.V(1).InfoS("DialResp has no receiver; dropped", "connectionID", resp.ConnectID, "dialID", resp.Random)
					return
				}
			}

			if resp.Error != "" {
				// On dial error, avoid leaking serve goroutine.
				return
			}

		case client.PacketType_DATA:
			resp := pkt.GetData()
			// TODO: flow control
			t.connsLock.RLock()
			conn, ok := t.conns[resp.ConnectID]
			t.connsLock.RUnlock()

			if ok {
				timer := time.NewTimer((time.Duration)(t.readTimeoutSeconds) * time.Second)
				select {
				case conn.readCh <- resp.Data:
					timer.Stop()
				case <-timer.C:
					klog.ErrorS(fmt.Errorf("timeout"), "readTimeout has been reached, the grpc connection to the proxy server will be closed", "connectionID", conn.connID, "readTimeoutSeconds", t.readTimeoutSeconds)
					return
				}
			} else {
				klog.V(1).InfoS("connection not recognized", "connectionID", resp.ConnectID)
			}
		case client.PacketType_CLOSE_RSP:
			resp := pkt.GetCloseResponse()
			t.connsLock.RLock()
			conn, ok := t.conns[resp.ConnectID]
			t.connsLock.RUnlock()

			if ok {
				close(conn.readCh)
				conn.closeCh <- resp.Error
				close(conn.closeCh)
				t.connsLock.Lock()
				delete(t.conns, resp.ConnectID)
				t.connsLock.Unlock()
				return
			}
			klog.V(1).InfoS("connection not recognized", "connectionID", resp.ConnectID)
		}
	}
}

// Dial connects to the address on the named network, similar to
// what net.Dial does. The only supported protocol is tcp.
func (t *grpcTunnel) DialContext(ctx context.Context, protocol, address string) (net.Conn, error) {
	if protocol != "tcp" {
		return nil, errors.New("protocol not supported")
	}

	random := rand.Int63() /* #nosec G404 */
	// This channel MUST NOT be buffered. The sender needs to know when we are not receiving things, so they can abort.
	resCh := make(chan dialResult)
	t.pendingDialLock.Lock()
	t.pendingDial[random] = resCh
	t.pendingDialLock.Unlock()
	defer func() {
		t.pendingDialLock.Lock()
		delete(t.pendingDial, random)
		t.pendingDialLock.Unlock()
	}()

	req := &client.Packet{
		Type: client.PacketType_DIAL_REQ,
		Payload: &client.Packet_DialRequest{
			DialRequest: &client.DialRequest{
				Protocol: protocol,
				Address:  address,
				Random:   random,
			},
		},
	}
	klog.V(5).InfoS("[tracing] send packet", "type", req.Type)

	err := t.stream.Send(req)
	if err != nil {
		return nil, err
	}

	klog.V(5).Infoln("DIAL_REQ sent to proxy server")

	c := &conn{stream: t.stream, random: random}

	select {
	case res := <-resCh:
		if res.err != "" {
			return nil, errors.New(res.err)
		}
		c.connID = res.connid
		c.readCh = make(chan []byte, 10)
		c.closeCh = make(chan string, 1)
		t.connsLock.Lock()
		t.conns[res.connid] = c
		t.connsLock.Unlock()
	case <-time.After(30 * time.Second):
		klog.V(5).InfoS("Timed out waiting for DialResp", "dialID", random)
		return nil, errors.New("dial timeout, backstop")
	case <-ctx.Done():
		klog.V(5).InfoS("Context canceled waiting for DialResp", "ctxErr", ctx.Err(), "dialID", random)
		return nil, errors.New("dial timeout, context")
	}

	return c, nil
}
<|endoftext|>


<commit_before>package server

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

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

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

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

type handshakeResp struct {
	Implements []string
}

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

type mountRequest struct {
	Name string
	ID   string
}

type volumeResponse struct {
	Err string
}

type volumePathResponse struct {
	Mountpoint string
	volumeResponse
}

type volumeInfo struct {
	Name       string
	Mountpoint string
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

func (d *driver) mountpath(request *mountRequest) string {
	if len(request.ID) != 0 {
		return path.Join(config.MountBase, request.Name+"_"+request.ID)
	}
	return path.Join(config.MountBase, request.Name)
}

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

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

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

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

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

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

	// If this is a block driver, first attach the volume.
	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		attachPath, err := v.Attach(vol.Id)
		if err != nil {
			if err == volume.ErrVolAttachedOnRemoteNode {
				d.logRequest(method, request.Name).Infof("Volume is attached on a remote node... will attempt to mount it.")
			} else {
				d.logRequest(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
				d.errorResponse(w, err)
				return
			}
		} else {
			d.logRequest(method, request.Name).Debugf("response %v", attachPath)
		}
	}

	// Now mount it.
	response.Mountpoint = d.mountpath(request)
	os.MkdirAll(response.Mountpoint, 0755)

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

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

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

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

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

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

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

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

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

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

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

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

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

	volInfo := volumeInfo{Name: request.Name}
	if len(vol.AttachPath) > 0 || len(vol.AttachPath) > 0 {
		volInfo.Mountpoint = path.Join(vol.AttachPath[0], config.DataDir)
	}

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

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

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

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

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

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

	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		_ = v.Detach(vol.Id)
	}
	d.emptyResponse(w)
}
<commit_msg>Initialize HALevel to 1<commit_after>package server

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

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

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

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

type handshakeResp struct {
	Implements []string
}

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

type mountRequest struct {
	Name string
	ID   string
}

type volumeResponse struct {
	Err string
}

type volumePathResponse struct {
	Mountpoint string
	volumeResponse
}

type volumeInfo struct {
	Name       string
	Mountpoint string
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

func (d *driver) mountpath(request *mountRequest) string {
	if len(request.ID) != 0 {
		return path.Join(config.MountBase, request.Name+"_"+request.ID)
	}
	return path.Join(config.MountBase, request.Name)
}

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

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

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

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

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

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

	// If this is a block driver, first attach the volume.
	if v.Type() == api.DriverType_DRIVER_TYPE_BLOCK {
		attachPath, err := v.Attach(vol.Id)
		if err != nil {
			if err == volume.ErrVolAttachedOnRemoteNode {
				d.logRequest(method, request.Name).Infof("Volume is attached on a remote node... will attempt to mount it.")
			} else {
				d.logRequest(method, request.Name).Warnf("Cannot attach volume: %v", err.Error())
				d.errorResponse(w, err)
				return
			}
		} else {
			d.logRequest(method, request.Name).Debugf("response %v", attachPath)
		}
	}

	// Now mount it.
	response.Mountpoint = d.mountpath(request)
	os.MkdirAll(response.Mountpoint, 0755)

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

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

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

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

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

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

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

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

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

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

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

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

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

	volInfo := volumeInfo{Name: request.Name}
	if len(vol.AttachPath) > 0 || len(vol.AttachPath) > 0 {
		volInfo.Mountpoint = path.Join(vol.AttachPath[0], config.DataDir)
	}

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

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

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

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

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

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

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


<commit_before>package server

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

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

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

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

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
}

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

type volumeInfo struct {
	vol *types.Volume
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if v.Type()&volume.Block != 0 {
		_ = v.Detach(volInfo.vol.ID)
	}
	d.emptyResponse(w)
}
<commit_msg>append datadir in Path request as well<commit_after>package server

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

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

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

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

type handshakeResp struct {
	Implements []string
}

type volumeRequest struct {
	Name string
}

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

type volumeInfo struct {
	vol *types.Volume
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


<commit_before>package httptoo

import (
	"bufio"
	"encoding/gob"
	"io"
	"net"
	"net/http"
	"net/url"
	"sync"
)

func deepCopy(dst, src interface{}) error {
	r, w := io.Pipe()
	e := gob.NewEncoder(w)
	d := gob.NewDecoder(r)
	var decErr, encErr error
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		decErr = d.Decode(dst)
		r.Close()
	}()
	encErr = e.Encode(src)
	// Always returns nil.
	w.CloseWithError(encErr)
	wg.Wait()
	if encErr != nil {
		return encErr
	}
	return decErr
}

// Takes a request, and alters its destination fields, for proxying.
func RedirectedRequest(r *http.Request, newUrl string) (ret *http.Request, err error) {
	u, err := url.Parse(newUrl)
	if err != nil {
		return
	}
	ret = new(http.Request)
	*ret = *r
	ret.Header = nil
	err = deepCopy(&ret.Header, r.Header)
	if err != nil {
		return
	}
	ret.URL = u
	ret.RequestURI = ""
	return
}

func ForwardResponse(w http.ResponseWriter, r *http.Response) {
	for h, vs := range r.Header {
		for _, v := range vs {
			w.Header().Add(h, v)
		}
	}
	w.WriteHeader(r.StatusCode)
	// Errors frequently occur writing the body when the client hangs up.
	io.Copy(w, r.Body)
	r.Body.Close()
}

func SetOriginRequestForwardingHeaders(o, f *http.Request) {
	xff := o.Header.Get("X-Forwarded-For")
	hop, _, _ := net.SplitHostPort(f.RemoteAddr)
	if xff == "" {
		xff = hop
	} else {
		xff += "," + hop
	}
	o.Header.Set("X-Forwarded-For", xff)
	o.Header.Set("X-Forwarded-Proto", OriginatingProtocol(f))
}

// w is for the client response. r is the request to send to the origin
// (already "forwarded"). originUrl is where to send the request.
func ReverseProxyUpgrade(w http.ResponseWriter, r *http.Request, originUrl string) (err error) {
	u, err := url.Parse(originUrl)
	if err != nil {
		return
	}
	oc, err := net.Dial("tcp", u.Host)
	if err != nil {
		return
	}
	defer oc.Close()
	err = r.Write(oc)
	if err != nil {
		return
	}
	originConnReadBuffer := bufio.NewReader(oc)
	originResp, err := http.ReadResponse(originConnReadBuffer, r)
	if err != nil {
		return
	}
	if originResp.StatusCode != 101 {
		ForwardResponse(w, originResp)
		return
	}
	cc, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		return
	}
	defer cc.Close()
	originResp.Write(cc)
	go io.Copy(oc, cc)
	// Let the origin connection control when this routine returns, as we
	// should trust it more.
	io.Copy(cc, originConnReadBuffer)
	return
}

func ReverseProxy(w http.ResponseWriter, r *http.Request, originUrl string, client *http.Client) (err error) {
	originRequest, err := RedirectedRequest(r, originUrl)
	if err != nil {
		return
	}
	SetOriginRequestForwardingHeaders(originRequest, r)
	if r.Header.Get("Connection") == "Upgrade" {
		return ReverseProxyUpgrade(w, originRequest, originUrl)
	}
	if client == nil {
		client = http.DefaultClient
	}
	originResp, err := client.Do(originRequest)
	if err != nil {
		return
	}
	ForwardResponse(w, originResp)
	return
}
<commit_msg>httptoo.ReverseProxy: Don't treat nil as http.DefaultClient<commit_after>package httptoo

import (
	"bufio"
	"encoding/gob"
	"io"
	"net"
	"net/http"
	"net/url"
	"sync"
)

func deepCopy(dst, src interface{}) error {
	r, w := io.Pipe()
	e := gob.NewEncoder(w)
	d := gob.NewDecoder(r)
	var decErr, encErr error
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		decErr = d.Decode(dst)
		r.Close()
	}()
	encErr = e.Encode(src)
	// Always returns nil.
	w.CloseWithError(encErr)
	wg.Wait()
	if encErr != nil {
		return encErr
	}
	return decErr
}

// Takes a request, and alters its destination fields, for proxying.
func RedirectedRequest(r *http.Request, newUrl string) (ret *http.Request, err error) {
	u, err := url.Parse(newUrl)
	if err != nil {
		return
	}
	ret = new(http.Request)
	*ret = *r
	ret.Header = nil
	err = deepCopy(&ret.Header, r.Header)
	if err != nil {
		return
	}
	ret.URL = u
	ret.RequestURI = ""
	return
}

func ForwardResponse(w http.ResponseWriter, r *http.Response) {
	for h, vs := range r.Header {
		for _, v := range vs {
			w.Header().Add(h, v)
		}
	}
	w.WriteHeader(r.StatusCode)
	// Errors frequently occur writing the body when the client hangs up.
	io.Copy(w, r.Body)
	r.Body.Close()
}

func SetOriginRequestForwardingHeaders(o, f *http.Request) {
	xff := o.Header.Get("X-Forwarded-For")
	hop, _, _ := net.SplitHostPort(f.RemoteAddr)
	if xff == "" {
		xff = hop
	} else {
		xff += "," + hop
	}
	o.Header.Set("X-Forwarded-For", xff)
	o.Header.Set("X-Forwarded-Proto", OriginatingProtocol(f))
}

// w is for the client response. r is the request to send to the origin
// (already "forwarded"). originUrl is where to send the request.
func ReverseProxyUpgrade(w http.ResponseWriter, r *http.Request, originUrl string) (err error) {
	u, err := url.Parse(originUrl)
	if err != nil {
		return
	}
	oc, err := net.Dial("tcp", u.Host)
	if err != nil {
		return
	}
	defer oc.Close()
	err = r.Write(oc)
	if err != nil {
		return
	}
	originConnReadBuffer := bufio.NewReader(oc)
	originResp, err := http.ReadResponse(originConnReadBuffer, r)
	if err != nil {
		return
	}
	if originResp.StatusCode != 101 {
		ForwardResponse(w, originResp)
		return
	}
	cc, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		return
	}
	defer cc.Close()
	originResp.Write(cc)
	go io.Copy(oc, cc)
	// Let the origin connection control when this routine returns, as we
	// should trust it more.
	io.Copy(cc, originConnReadBuffer)
	return
}

func ReverseProxy(w http.ResponseWriter, r *http.Request, originUrl string, client *http.Client) (err error) {
	originRequest, err := RedirectedRequest(r, originUrl)
	if err != nil {
		return
	}
	SetOriginRequestForwardingHeaders(originRequest, r)
	if r.Header.Get("Connection") == "Upgrade" {
		return ReverseProxyUpgrade(w, originRequest, originUrl)
	}
	originResp, err := client.Do(originRequest)
	if err != nil {
		return
	}
	ForwardResponse(w, originResp)
	return
}
<|endoftext|>


<commit_before>package httpfile

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"sync"
	"time"

	"github.com/go-errors/errors"
	uuid "github.com/satori/go.uuid"
)

// A GetURLFunc returns a URL we can download the resource from.
// It's handy to have this as a function rather than a constant for signed expiring URLs
type GetURLFunc func() (urlString string, err error)

// A NeedsRenewalFunc analyzes an HTTP request and returns true if it needs to be renewed
type NeedsRenewalFunc func(req *http.Request) bool

// A LogFunc prints debug message
type LogFunc func(msg string)

// amount we're willing to download and throw away
const maxDiscard int64 = 1 * 1024 * 1024 // 1MB

var ErrNotFound = errors.New("HTTP file not found on server")

type HTTPFile struct {
	getURL       GetURLFunc
	needsRenewal NeedsRenewalFunc
	client       *http.Client

	Log LogFunc

	name   string
	size   int64
	offset int64 // for io.ReadSeeker

	ReaderStaleThreshold time.Duration

	closed bool

	readers      map[string]*httpReader
	readersMutex sync.Mutex
}

type httpReader struct {
	file      *HTTPFile
	id        string
	touchedAt time.Time
	offset    int64
	body      io.ReadCloser
	reader    *bufio.Reader
}

const DefaultReaderStaleThreshold = time.Second * time.Duration(10)

func (hr *httpReader) Stale() bool {
	return time.Since(hr.touchedAt) > hr.file.ReaderStaleThreshold
}

func (hr *httpReader) Read(data []byte) (int, error) {
	hr.touchedAt = time.Now()
	readBytes, err := hr.reader.Read(data)
	hr.offset += int64(readBytes)

	if err != nil {
		return readBytes, errors.Wrap(err, 1)
	}
	return readBytes, nil
}

func (hr *httpReader) Discard(n int) (int, error) {
	hr.touchedAt = time.Now()
	discarded, err := hr.reader.Discard(n)
	hr.offset += int64(discarded)

	if err != nil {
		return discarded, errors.Wrap(err, 1)
	}
	return discarded, nil
}

func (hr *httpReader) Connect() error {
	if hr.body != nil {
		err := hr.body.Close()
		if err != nil {
			return err
		}

		hr.body = nil
		hr.reader = nil
	}

	urlStr, err := hr.file.getURL()
	if err != nil {
		return errors.Wrap(err, 1)
	}

	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	byteRange := fmt.Sprintf("bytes=%d-", hr.offset)
	req.Header.Set("Range", byteRange)

	res, err := hr.file.client.Do(req)
	if err != nil {
		return errors.Wrap(err, 1)
	}
	hr.file.log("did request, status %d", res.StatusCode)

	if res.StatusCode == 200 && hr.offset > 0 {
		err = fmt.Errorf("HTTP Range header not supported by %s, bailing out", req.Host)
		return errors.Wrap(err, 1)
	}

	if res.StatusCode/100 != 2 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			body = []byte("could not read error body")
			err = nil
		}

		err = fmt.Errorf("HTTP %d returned by %s (%s), bailing out", res.StatusCode, req.Host, string(body))
		return errors.Wrap(err, 1)
	}

	hr.reader = bufio.NewReaderSize(res.Body, int(maxDiscard))
	hr.body = res.Body

	return nil
}

func (hr *httpReader) Close() error {
	err := hr.body.Close()

	if err != nil {
		return errors.Wrap(err, 1)
	}
	return nil
}

var _ io.Seeker = (*HTTPFile)(nil)
var _ io.Reader = (*HTTPFile)(nil)
var _ io.ReaderAt = (*HTTPFile)(nil)
var _ io.Closer = (*HTTPFile)(nil)

func New(getURL GetURLFunc, needsRenewal NeedsRenewalFunc, client *http.Client) (*HTTPFile, error) {
	urlStr, err := getURL()
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	parsedUrl, err := url.Parse(urlStr)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	req, err := http.NewRequest("HEAD", urlStr, nil)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	res, err := client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	if res.StatusCode != 200 {
		if res.StatusCode == 404 {
			return nil, errors.Wrap(ErrNotFound, 1)
		}

		err = fmt.Errorf("Expected HTTP 200, got HTTP %d for %s", res.StatusCode, urlStr)
		return nil, errors.Wrap(err, 1)
	}

	hf := &HTTPFile{
		getURL:       getURL,
		needsRenewal: needsRenewal,
		client:       client,

		name:    parsedUrl.Path,
		size:    res.ContentLength,
		readers: make(map[string]*httpReader),

		ReaderStaleThreshold: DefaultReaderStaleThreshold,
	}
	return hf, nil
}

func (hf *HTTPFile) NumReaders() int {
	return len(hf.readers)
}

func (hf *HTTPFile) borrowReader(offset int64) (*httpReader, error) {
	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	var bestReader string
	var bestDiff int64 = math.MaxInt64

	for _, reader := range hf.readers {
		if reader.Stale() {
			delete(hf.readers, reader.id)

			err := reader.Close()
			if err != nil {
				return nil, errors.Wrap(err, 1)
			}
			continue
		}

		diff := offset - reader.offset
		if diff >= 0 && diff < maxDiscard {
			if diff < bestDiff {
				bestReader = reader.id
				bestDiff = diff
			}
		}
	}

	if bestReader != "" {
		// re-use!
		reader := hf.readers[bestReader]
		delete(hf.readers, bestReader)

		// discard if needed
		if bestDiff > 0 {
			hf.log("borrow: for %d, re-using %d by discarding %d bytes", offset, reader.offset, bestDiff)

			// XXX: not int64-clean
			_, err := reader.Discard(int(bestDiff))
			if err != nil {
				return nil, errors.Wrap(err, 1)
			}
		}

		return reader, nil
	}

	// provision a new reader
	hf.log("borrow: making fresh for offset %d", offset)

	reader := &httpReader{
		file:      hf,
		id:        uuid.NewV4().String(),
		touchedAt: time.Now(),
		offset:    offset,
	}

	err := reader.Connect()
	if err != nil {
		return nil, err
	}

	return reader, nil
}

func (hf *HTTPFile) returnReader(reader *httpReader) {
	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	// TODO: enforce max idle readers ?

	reader.touchedAt = time.Now()
	hf.readers[reader.id] = reader
}

func (hf *HTTPFile) Stat() (os.FileInfo, error) {
	return &httpFileInfo{hf}, nil
}

func (hf *HTTPFile) Seek(offset int64, whence int) (int64, error) {
	var newOffset int64

	switch whence {
	case os.SEEK_SET:
		newOffset = offset
	case os.SEEK_END:
		newOffset = hf.size + offset
	case os.SEEK_CUR:
		newOffset = hf.offset + offset
	default:
		return hf.offset, fmt.Errorf("invalid whence value %d", whence)
	}

	if newOffset < 0 {
		newOffset = 0
	}

	if newOffset > hf.size {
		newOffset = hf.size
	}

	hf.offset = newOffset
	return hf.offset, nil
}

func (hf *HTTPFile) Read(data []byte) (int, error) {
	hf.log("Read(%d)", len(data))

	reader, err := hf.borrowReader(hf.offset)
	if err != nil {
		return 0, errors.Wrap(err, 1)
	}

	defer hf.returnReader(reader)

	bytesRead, err := reader.Read(data)
	hf.offset += int64(bytesRead)

	if err != nil {
		return bytesRead, errors.Wrap(err, 1)
	}
	return bytesRead, nil
}

func (hf *HTTPFile) ReadAt(data []byte, offset int64) (int, error) {
	hf.log("ReadAt(%d, %d)", len(data), offset)

	reader, err := hf.borrowReader(offset)
	if err != nil {
		return 0, errors.Wrap(err, 1)
	}

	defer hf.returnReader(reader)

	totalBytesRead := 0
	bytesToRead := len(data)

	for totalBytesRead < bytesToRead {
		bytesRead, err := reader.Read(data[totalBytesRead:])
		hf.offset += int64(bytesRead)
		totalBytesRead += bytesRead

		if err != nil {
			if errors.Is(err, io.ErrUnexpectedEOF) {
				log.Printf("\n\nGot unexpected eof, retrying\n\n")
				err := reader.Connect()
				if err != nil {
					return totalBytesRead, err
				}
			} else {
				return totalBytesRead, errors.Wrap(err, 1)
			}
		}
	}

	return totalBytesRead, nil
}

func (hf *HTTPFile) Close() error {
	if hf.closed {
		return nil
	}

	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	for id, reader := range hf.readers {
		err := reader.Close()
		if err != nil {
			return errors.Wrap(err, 1)
		}

		delete(hf.readers, id)
	}

	hf.closed = true

	return nil
}

func (hf *HTTPFile) log(format string, args ...interface{}) {
	if hf.Log == nil {
		return
	}

	hf.Log(fmt.Sprintf(format, args...))
}
<commit_msg>Stop wrapping errors, it breaks io interfaces<commit_after>package httpfile

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"sync"
	"time"

	"github.com/go-errors/errors"
	uuid "github.com/satori/go.uuid"
)

// A GetURLFunc returns a URL we can download the resource from.
// It's handy to have this as a function rather than a constant for signed expiring URLs
type GetURLFunc func() (urlString string, err error)

// A NeedsRenewalFunc analyzes an HTTP request and returns true if it needs to be renewed
type NeedsRenewalFunc func(req *http.Request) bool

// A LogFunc prints debug message
type LogFunc func(msg string)

// amount we're willing to download and throw away
const maxDiscard int64 = 1 * 1024 * 1024 // 1MB

var ErrNotFound = errors.New("HTTP file not found on server")

type HTTPFile struct {
	getURL       GetURLFunc
	needsRenewal NeedsRenewalFunc
	client       *http.Client

	Log LogFunc

	name   string
	size   int64
	offset int64 // for io.ReadSeeker

	ReaderStaleThreshold time.Duration

	closed bool

	readers      map[string]*httpReader
	readersMutex sync.Mutex
}

type httpReader struct {
	file      *HTTPFile
	id        string
	touchedAt time.Time
	offset    int64
	body      io.ReadCloser
	reader    *bufio.Reader
}

const DefaultReaderStaleThreshold = time.Second * time.Duration(10)

func (hr *httpReader) Stale() bool {
	return time.Since(hr.touchedAt) > hr.file.ReaderStaleThreshold
}

func (hr *httpReader) Read(data []byte) (int, error) {
	hr.touchedAt = time.Now()
	readBytes, err := hr.reader.Read(data)
	hr.offset += int64(readBytes)

	if err != nil {
		return readBytes, err
	}
	return readBytes, nil
}

func (hr *httpReader) Discard(n int) (int, error) {
	hr.touchedAt = time.Now()
	discarded, err := hr.reader.Discard(n)
	hr.offset += int64(discarded)

	if err != nil {
		return discarded, err
	}
	return discarded, nil
}

func (hr *httpReader) Connect() error {
	if hr.body != nil {
		err := hr.body.Close()
		if err != nil {
			return err
		}

		hr.body = nil
		hr.reader = nil
	}

	urlStr, err := hr.file.getURL()
	if err != nil {
		return err
	}

	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		return err
	}

	byteRange := fmt.Sprintf("bytes=%d-", hr.offset)
	req.Header.Set("Range", byteRange)

	res, err := hr.file.client.Do(req)
	if err != nil {
		return err
	}
	hr.file.log("did request, status %d", res.StatusCode)

	if res.StatusCode == 200 && hr.offset > 0 {
		err = fmt.Errorf("HTTP Range header not supported by %s, bailing out", req.Host)
		return err
	}

	if res.StatusCode/100 != 2 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			body = []byte("could not read error body")
			err = nil
		}

		err = fmt.Errorf("HTTP %d returned by %s (%s), bailing out", res.StatusCode, req.Host, string(body))
		return err
	}

	hr.reader = bufio.NewReaderSize(res.Body, int(maxDiscard))
	hr.body = res.Body

	return nil
}

func (hr *httpReader) Close() error {
	err := hr.body.Close()

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

var _ io.Seeker = (*HTTPFile)(nil)
var _ io.Reader = (*HTTPFile)(nil)
var _ io.ReaderAt = (*HTTPFile)(nil)
var _ io.Closer = (*HTTPFile)(nil)

func New(getURL GetURLFunc, needsRenewal NeedsRenewalFunc, client *http.Client) (*HTTPFile, error) {
	urlStr, err := getURL()
	if err != nil {
		return nil, err
	}

	parsedUrl, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("HEAD", urlStr, nil)
	if err != nil {
		return nil, err
	}

	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if res.StatusCode != 200 {
		if res.StatusCode == 404 {
			return nil, errors.Wrap(ErrNotFound, 1)
		}

		err = fmt.Errorf("Expected HTTP 200, got HTTP %d for %s", res.StatusCode, urlStr)
		return nil, err
	}

	hf := &HTTPFile{
		getURL:       getURL,
		needsRenewal: needsRenewal,
		client:       client,

		name:    parsedUrl.Path,
		size:    res.ContentLength,
		readers: make(map[string]*httpReader),

		ReaderStaleThreshold: DefaultReaderStaleThreshold,
	}
	return hf, nil
}

func (hf *HTTPFile) NumReaders() int {
	return len(hf.readers)
}

func (hf *HTTPFile) borrowReader(offset int64) (*httpReader, error) {
	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	var bestReader string
	var bestDiff int64 = math.MaxInt64

	for _, reader := range hf.readers {
		if reader.Stale() {
			delete(hf.readers, reader.id)

			err := reader.Close()
			if err != nil {
				return nil, err
			}
			continue
		}

		diff := offset - reader.offset
		if diff >= 0 && diff < maxDiscard {
			if diff < bestDiff {
				bestReader = reader.id
				bestDiff = diff
			}
		}
	}

	if bestReader != "" {
		// re-use!
		reader := hf.readers[bestReader]
		delete(hf.readers, bestReader)

		// discard if needed
		if bestDiff > 0 {
			hf.log("borrow: for %d, re-using %d by discarding %d bytes", offset, reader.offset, bestDiff)

			// XXX: not int64-clean
			_, err := reader.Discard(int(bestDiff))
			if err != nil {
				return nil, err
			}
		}

		return reader, nil
	}

	// provision a new reader
	hf.log("borrow: making fresh for offset %d", offset)

	reader := &httpReader{
		file:      hf,
		id:        uuid.NewV4().String(),
		touchedAt: time.Now(),
		offset:    offset,
	}

	err := reader.Connect()
	if err != nil {
		return nil, err
	}

	return reader, nil
}

func (hf *HTTPFile) returnReader(reader *httpReader) {
	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	// TODO: enforce max idle readers ?

	reader.touchedAt = time.Now()
	hf.readers[reader.id] = reader
}

func (hf *HTTPFile) Stat() (os.FileInfo, error) {
	return &httpFileInfo{hf}, nil
}

func (hf *HTTPFile) Seek(offset int64, whence int) (int64, error) {
	var newOffset int64

	switch whence {
	case os.SEEK_SET:
		newOffset = offset
	case os.SEEK_END:
		newOffset = hf.size + offset
	case os.SEEK_CUR:
		newOffset = hf.offset + offset
	default:
		return hf.offset, fmt.Errorf("invalid whence value %d", whence)
	}

	if newOffset < 0 {
		newOffset = 0
	}

	if newOffset > hf.size {
		newOffset = hf.size
	}

	hf.offset = newOffset
	return hf.offset, nil
}

func (hf *HTTPFile) Read(data []byte) (int, error) {
	hf.log("Read(%d)", len(data))

	reader, err := hf.borrowReader(hf.offset)
	if err != nil {
		return 0, err
	}

	defer hf.returnReader(reader)

	bytesRead, err := reader.Read(data)
	hf.offset += int64(bytesRead)

	if err != nil {
		return bytesRead, err
	}
	return bytesRead, nil
}

func (hf *HTTPFile) ReadAt(data []byte, offset int64) (int, error) {
	hf.log("ReadAt(%d, %d)", len(data), offset)

	reader, err := hf.borrowReader(offset)
	if err != nil {
		return 0, err
	}

	defer hf.returnReader(reader)

	totalBytesRead := 0
	bytesToRead := len(data)

	for totalBytesRead < bytesToRead {
		bytesRead, err := reader.Read(data[totalBytesRead:])
		hf.offset += int64(bytesRead)
		totalBytesRead += bytesRead

		if err != nil {
			if errors.Is(err, io.ErrUnexpectedEOF) {
				log.Printf("\n\nGot unexpected eof, retrying\n\n")
				err = reader.Connect()
				if err != nil {
					return totalBytesRead, err
				}
			} else {
				return totalBytesRead, err
			}
		}
	}

	return totalBytesRead, nil
}

func (hf *HTTPFile) Close() error {
	if hf.closed {
		return nil
	}

	hf.readersMutex.Lock()
	defer hf.readersMutex.Unlock()

	for id, reader := range hf.readers {
		err := reader.Close()
		if err != nil {
			return err
		}

		delete(hf.readers, id)
	}

	hf.closed = true

	return nil
}

func (hf *HTTPFile) log(format string, args ...interface{}) {
	if hf.Log == nil {
		return
	}

	hf.Log(fmt.Sprintf(format, args...))
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/Cox-Automotive/alks-go"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type TagMap map[string]interface{}
type IgnoreTags struct {
	Keys        TagMap
	KeyPrefixes TagMap
}

func TagsSchema() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeMap,
		Optional: true,
		Elem:     &schema.Schema{Type: schema.TypeString},
	}
}

func TagsSchemaComputed() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeMap,
		Optional: true,
		Computed: true,
		Elem:     &schema.Schema{Type: schema.TypeString},
	}
}

func SetTagsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
	defaultTags := meta.(*AlksClient).defaultTags
	ignoredTags := meta.(*AlksClient).ignoreTags
	resourceTags := (diff.Get("tags")).(map[string]interface{})
	//default tag values will be overwritten by resource values if key exists in both maps
	allTags := combineTagMaps(defaultTags, resourceTags)
	localTags := removeIgnoredTags(allTags, *ignoredTags)

	// To ensure "tags_all" is correctly computed, we explicitly set the attribute diff
	// when the merger of resource-level tags onto provider-level tags results in n > 0 tags,
	// otherwise we mark the attribute as "Computed" only when their is a known diff (excluding an empty map)
	// or a change for "tags_all".

	if len(localTags) > 0 {
		if err := diff.SetNew("tags_all", localTags); err != nil {
			return fmt.Errorf("error setting new tags_all diff: %w", err)
		}
	} else if len(diff.Get("tags_all").(map[string]interface{})) > 0 {
		if err := diff.SetNewComputed("tags_all"); err != nil {
			return fmt.Errorf("error setting tags_all to computed: %w", err)
		}
	} else if diff.HasChange("tags_all") {
		if err := diff.SetNewComputed("tags_all"); err != nil {
			return fmt.Errorf("error setting tags_all to computed: %w", err)
		}
	}

	return nil
}

//Removes default tags from a map of role specific + default tags
func removeDefaultTags(allTags TagMap, defaultTags TagMap) TagMap {
	for k, v := range defaultTags {
		//If the key and value of a tag returned from the role exists in the defaultTags list
		//We will assume it was set as a default tag and remove it from role specific tag list
		if val, ok := allTags[k]; ok {
			if val == v {
				delete(allTags, k)
			}
		}

	}
	return allTags
}

func removeIgnoredTags(allTags TagMap, ignoredTags IgnoreTags) TagMap {
	localMap := TagMap{}
	for k, v := range allTags {
		localMap[k] = v.(string)
	}

	for k := range allTags {
		if _, ok := ignoredTags.Keys[k]; ok {
			delete(localMap, k)
		} else {
			for kp := range ignoredTags.KeyPrefixes {
				if strings.HasPrefix(k, kp) {
					delete(localMap, k)
				}
			}
		}

	}
	return localMap
}

func tagMapToSlice(tagMap TagMap) []alks.Tag {
	tags := []alks.Tag{}
	for k, v := range tagMap {
		tag := alks.Tag{Key: k, Value: v.(string)}
		tags = append(tags, tag)
	}
	return tags
}

func tagSliceToMap(tagSlice []alks.Tag) TagMap {
	tagMap := make(TagMap)
	for _, t := range tagSlice {
		tagMap[t.Key] = t.Value
	}
	return tagMap
}

func getExternalyManagedTags(roleTags TagMap, ignoredTags IgnoreTags) TagMap {
	externalTags := TagMap{}
	//Loop Through ignored keys and ignored key prefixes, checking if a tag exists that should be ignored
	for k := range ignoredTags.Keys {
		if val, ok := roleTags[k]; ok {
			externalTags[k] = val.(string)
		}
	}

	for p := range ignoredTags.KeyPrefixes {
		for k, v := range roleTags {
			if strings.HasPrefix(k, p) {
				externalTags[k] = v.(string)
			}
		}
	}
	return externalTags
}

//Combine two tag maps.  Values in map2 will overwrite values in map1 if they exist in both maps
func combineTagMaps(map1 TagMap, map2 TagMap) TagMap {
	LocalMap := TagMap{}

	for k, v := range map1 {
		LocalMap[k] = v
	}
	for k, v := range map2 {
		LocalMap[k] = v
	}

	return LocalMap
}
<commit_msg>Changed names of arguments in combineTagMap<commit_after>package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/Cox-Automotive/alks-go"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type TagMap map[string]interface{}
type IgnoreTags struct {
	Keys        TagMap
	KeyPrefixes TagMap
}

func TagsSchema() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeMap,
		Optional: true,
		Elem:     &schema.Schema{Type: schema.TypeString},
	}
}

func TagsSchemaComputed() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeMap,
		Optional: true,
		Computed: true,
		Elem:     &schema.Schema{Type: schema.TypeString},
	}
}

func SetTagsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
	defaultTags := meta.(*AlksClient).defaultTags
	ignoredTags := meta.(*AlksClient).ignoreTags
	resourceTags := (diff.Get("tags")).(map[string]interface{})
	//default tag values will be overwritten by resource values if key exists in both maps
	allTags := combineTagMaps(defaultTags, resourceTags)
	localTags := removeIgnoredTags(allTags, *ignoredTags)

	// To ensure "tags_all" is correctly computed, we explicitly set the attribute diff
	// when the merger of resource-level tags onto provider-level tags results in n > 0 tags,
	// otherwise we mark the attribute as "Computed" only when their is a known diff (excluding an empty map)
	// or a change for "tags_all".

	if len(localTags) > 0 {
		if err := diff.SetNew("tags_all", localTags); err != nil {
			return fmt.Errorf("error setting new tags_all diff: %w", err)
		}
	} else if len(diff.Get("tags_all").(map[string]interface{})) > 0 {
		if err := diff.SetNewComputed("tags_all"); err != nil {
			return fmt.Errorf("error setting tags_all to computed: %w", err)
		}
	} else if diff.HasChange("tags_all") {
		if err := diff.SetNewComputed("tags_all"); err != nil {
			return fmt.Errorf("error setting tags_all to computed: %w", err)
		}
	}

	return nil
}

//Removes default tags from a map of role specific + default tags
func removeDefaultTags(allTags TagMap, defaultTags TagMap) TagMap {
	for k, v := range defaultTags {
		//If the key and value of a tag returned from the role exists in the defaultTags list
		//We will assume it was set as a default tag and remove it from role specific tag list
		if val, ok := allTags[k]; ok {
			if val == v {
				delete(allTags, k)
			}
		}

	}
	return allTags
}

func removeIgnoredTags(allTags TagMap, ignoredTags IgnoreTags) TagMap {
	localMap := TagMap{}
	for k, v := range allTags {
		localMap[k] = v.(string)
	}

	for k := range allTags {
		if _, ok := ignoredTags.Keys[k]; ok {
			delete(localMap, k)
		} else {
			for kp := range ignoredTags.KeyPrefixes {
				if strings.HasPrefix(k, kp) {
					delete(localMap, k)
				}
			}
		}

	}
	return localMap
}

func tagMapToSlice(tagMap TagMap) []alks.Tag {
	tags := []alks.Tag{}
	for k, v := range tagMap {
		tag := alks.Tag{Key: k, Value: v.(string)}
		tags = append(tags, tag)
	}
	return tags
}

func tagSliceToMap(tagSlice []alks.Tag) TagMap {
	tagMap := make(TagMap)
	for _, t := range tagSlice {
		tagMap[t.Key] = t.Value
	}
	return tagMap
}

func getExternalyManagedTags(roleTags TagMap, ignoredTags IgnoreTags) TagMap {
	externalTags := TagMap{}
	//Loop Through ignored keys and ignored key prefixes, checking if a tag exists that should be ignored
	for k := range ignoredTags.Keys {
		if val, ok := roleTags[k]; ok {
			externalTags[k] = val.(string)
		}
	}

	for p := range ignoredTags.KeyPrefixes {
		for k, v := range roleTags {
			if strings.HasPrefix(k, p) {
				externalTags[k] = v.(string)
			}
		}
	}
	return externalTags
}

//Combine two tag maps.  Values in map2 will overwrite values in map1 if they exist in both maps
func combineTagMaps(baseMap TagMap, mergeMap TagMap) TagMap {
	LocalMap := TagMap{}

	for k, v := range baseMap {
		LocalMap[k] = v
	}
	for k, v := range mergeMap {
		LocalMap[k] = v
	}

	return LocalMap
}
<|endoftext|>


<commit_before>package libkb

import (
	"fmt"
	"regexp"
	"strings"
)

type Identity struct {
	Username string
	Comment  string
	Email    string
}

func ParseIdentity(s string) (*Identity, error) {
	rxx := regexp.MustCompile(
		`^([^(<]*?)` + // The beginning name of the user (no comment or key)
			`(?:\s*\((.*?)\))?` + // The optional comment
			`(?:\s*<(.*?)>)?$`) // The optional email address
	v := rxx.FindStringSubmatch(s)
	if v == nil {
		return nil, fmt.Errorf("Bad PGP-style identity: %s", s)
	} else {
		ret := &Identity{
			Username: v[1],
			Comment:  v[2],
			Email:    v[3],
		}
		return ret, nil
	}
}

func (i Identity) Format() string {
	parts := make([]string, 0, 3)
	if len(i.Username) > 0 {
		parts = append(parts, i.Username)
	}
	if len(i.Comment) > 0 {
		parts = append(parts, "("+i.Comment+")")
	}
	if len(i.Email) > 0 {
		parts = append(parts, "<"+i.Email+">")
	}
	return strings.Join(parts, " ")
}
<commit_msg>better spacing<commit_after>package libkb

import (
	"fmt"
	"regexp"
	"strings"
)

type Identity struct {
	Username string
	Comment  string
	Email    string
}

func ParseIdentity(s string) (*Identity, error) {
	rxx := regexp.MustCompile("" +
		`^([^(<]*?)` + // The beginning name of the user (no comment or key)
		`(?:\s*\((.*?)\))?` + // The optional comment
		`(?:\s*<(.*?)>)?$`) // The optional email address
	v := rxx.FindStringSubmatch(s)
	if v == nil {
		return nil, fmt.Errorf("Bad PGP-style identity: %s", s)
	} else {
		ret := &Identity{
			Username: v[1],
			Comment:  v[2],
			Email:    v[3],
		}
		return ret, nil
	}
}

func (i Identity) Format() string {
	parts := make([]string, 0, 3)
	if len(i.Username) > 0 {
		parts = append(parts, i.Username)
	}
	if len(i.Comment) > 0 {
		parts = append(parts, "("+i.Comment+")")
	}
	if len(i.Email) > 0 {
		parts = append(parts, "<"+i.Email+">")
	}
	return strings.Join(parts, " ")
}
<|endoftext|>