<commit_before>// Licensed to Elasticsearch B.V. under one or more agreements.
// Elasticsearch B.V. licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

// +build integration

package estransport

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"testing"
	"time"
)

func NewServer(addr string, handler http.Handler) *http.Server {
	return &http.Server{Addr: addr, Handler: handler}
}

func TestStatusConnectionPool(t *testing.T) {
	defaultResurrectTimeoutInitial = time.Second
	defer func() { defaultResurrectTimeoutInitial = 60 * time.Second }()

	var (
		server      *http.Server
		servers     []*http.Server
		serverURLs  []*url.URL
		serverHosts []string
		numServers  = 3

		defaultHandler = func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "OK") }
	)

	for i := 1; i <= numServers; i++ {
		s := NewServer(fmt.Sprintf("localhost:1000%d", i), http.HandlerFunc(defaultHandler))

		go func(s *http.Server) {
			if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
				t.Fatalf("Unable to start server: %s", err)
			}
		}(s)

		defer func(s *http.Server) { s.Close() }(s)

		servers = append(servers, s)
		time.Sleep(time.Millisecond)
	}

	for _, s := range servers {
		u, _ := url.Parse("http://" + s.Addr)
		serverURLs = append(serverURLs, u)
		serverHosts = append(serverHosts, u.String())
	}

	fmt.Printf("==> Started %d servers on %s\n", numServers, serverHosts)

	transport, _ := New(
		Config{
			URLs:   serverURLs,
			Logger: &TextLogger{Output: os.Stdout},

			EnableDebugLogger: true,
		})

	pool := transport.pool.(*statusConnectionPool)

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 3 {
		t.Errorf("Unexpected number of live connections, want=3, got=%d", len(pool.live))
	}

	server = servers[1]
	fmt.Printf("==> Closing server: %s\n", server.Addr)
	if err := server.Close(); err != nil {
		t.Fatalf("Unable to close server: %s", err)
	}

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 2 {
		t.Errorf("Unexpected number of live connections, want=2, got=%d", len(pool.live))
	}

	if len(pool.dead) != 1 {
		t.Errorf("Unexpected number of dead connections, want=1, got=%d", len(pool.dead))
	}

	server = NewServer("localhost:10002", http.HandlerFunc(defaultHandler))
	servers[1] = server
	fmt.Printf("==> Starting server: %s\n", server.Addr)
	go func() {
		if err := server.ListenAndServe(); err != nil {
			t.Fatalf("Unable to start server: %s", err)
		}
	}()

	fmt.Println("==> Waiting 1.25s for resurrection...")
	time.Sleep(1250 * time.Millisecond)

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 3 {
		t.Errorf("Unexpected number of live connections, want=3, got=%d", len(pool.live))
	}

	if len(pool.dead) != 0 {
		t.Errorf("Unexpected number of dead connections, want=0, got=%d", len(pool.dead))
	}
}
<commit_msg>Transport: Disable logger in TestStatusConnectionPool in Github Actions environment<commit_after>// Licensed to Elasticsearch B.V. under one or more agreements.
// Elasticsearch B.V. licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

// +build integration

package estransport

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"testing"
	"time"
)

func NewServer(addr string, handler http.Handler) *http.Server {
	return &http.Server{Addr: addr, Handler: handler}
}

func TestStatusConnectionPool(t *testing.T) {
	defaultResurrectTimeoutInitial = time.Second
	defer func() { defaultResurrectTimeoutInitial = 60 * time.Second }()

	var (
		server      *http.Server
		servers     []*http.Server
		serverURLs  []*url.URL
		serverHosts []string
		numServers  = 3

		defaultHandler = func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "OK") }
	)

	for i := 1; i <= numServers; i++ {
		s := NewServer(fmt.Sprintf("localhost:1000%d", i), http.HandlerFunc(defaultHandler))

		go func(s *http.Server) {
			if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
				t.Fatalf("Unable to start server: %s", err)
			}
		}(s)

		defer func(s *http.Server) { s.Close() }(s)

		servers = append(servers, s)
		time.Sleep(time.Millisecond)
	}

	for _, s := range servers {
		u, _ := url.Parse("http://" + s.Addr)
		serverURLs = append(serverURLs, u)
		serverHosts = append(serverHosts, u.String())
	}

	fmt.Printf("==> Started %d servers on %s\n", numServers, serverHosts)

	cfg := Config{URLs: serverURLs}

	if _, ok := os.LookupEnv("GITHUB_ACTIONS"); !ok {
		cfg.Logger = &TextLogger{Output: os.Stdout}
		cfg.EnableDebugLogger = true
	}

	transport, _ := New(cfg)

	pool := transport.pool.(*statusConnectionPool)

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 3 {
		t.Errorf("Unexpected number of live connections, want=3, got=%d", len(pool.live))
	}

	server = servers[1]
	fmt.Printf("==> Closing server: %s\n", server.Addr)
	if err := server.Close(); err != nil {
		t.Fatalf("Unable to close server: %s", err)
	}

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 2 {
		t.Errorf("Unexpected number of live connections, want=2, got=%d", len(pool.live))
	}

	if len(pool.dead) != 1 {
		t.Errorf("Unexpected number of dead connections, want=1, got=%d", len(pool.dead))
	}

	server = NewServer("localhost:10002", http.HandlerFunc(defaultHandler))
	servers[1] = server
	fmt.Printf("==> Starting server: %s\n", server.Addr)
	go func() {
		if err := server.ListenAndServe(); err != nil {
			t.Fatalf("Unable to start server: %s", err)
		}
	}()

	fmt.Println("==> Waiting 1.25s for resurrection...")
	time.Sleep(1250 * time.Millisecond)

	for i := 1; i <= 9; i++ {
		req, _ := http.NewRequest("GET", "/", nil)
		res, err := transport.Perform(req)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
		}
		if res.StatusCode != 200 {
			t.Errorf("Unexpected status code, want=200, got=%d", res.StatusCode)
		}
	}

	if len(pool.live) != 3 {
		t.Errorf("Unexpected number of live connections, want=3, got=%d", len(pool.live))
	}

	if len(pool.dead) != 0 {
		t.Errorf("Unexpected number of dead connections, want=0, got=%d", len(pool.dead))
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsVpcPeeringConnection() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsVPCPeeringCreate,
		Read:   resourceAwsVPCPeeringRead,
		Update: resourceAwsVPCPeeringUpdate,
		Delete: resourceAwsVPCPeeringDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(1 * time.Minute),
			Update: schema.DefaultTimeout(1 * time.Minute),
			Delete: schema.DefaultTimeout(1 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"peer_owner_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"peer_vpc_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"vpc_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"auto_accept": {
				Type:     schema.TypeBool,
				Optional: true,
			},
			"accept_status": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"peer_region": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"accepter":  vpcPeeringConnectionOptionsSchema(),
			"requester": vpcPeeringConnectionOptionsSchema(),
			"tags":      tagsSchema(),
		},
	}
}

func resourceAwsVPCPeeringCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	// Create the vpc peering connection
	createOpts := &ec2.CreateVpcPeeringConnectionInput{
		PeerVpcId: aws.String(d.Get("peer_vpc_id").(string)),
		VpcId:     aws.String(d.Get("vpc_id").(string)),
	}

	if v, ok := d.GetOk("peer_owner_id"); ok {
		createOpts.PeerOwnerId = aws.String(v.(string))
	}

	if v, ok := d.GetOk("peer_region"); ok {
		if _, ok := d.GetOk("auto_accept"); ok {
			return fmt.Errorf("peer_region cannot be set whilst auto_accept is true when creating a vpc peering connection")
		}
		createOpts.PeerRegion = aws.String(v.(string))
	}

	log.Printf("[DEBUG] VPC Peering Create options: %#v", createOpts)

	resp, err := conn.CreateVpcPeeringConnection(createOpts)
	if err != nil {
		return errwrap.Wrapf("Error creating VPC Peering Connection: {{err}}", err)
	}

	// Get the ID and store it
	rt := resp.VpcPeeringConnection
	d.SetId(*rt.VpcPeeringConnectionId)
	log.Printf("[INFO] VPC Peering Connection ID: %s", d.Id())

	vpcAvailableErr := vpcPeeringConnectionWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate))
	if vpcAvailableErr != nil {
		return errwrap.Wrapf("Error waiting for VPC Peering Connection to become available: {{err}}", vpcAvailableErr)
	}

	return resourceAwsVPCPeeringUpdate(d, meta)
}

func resourceAwsVPCPeeringRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AWSClient)

	pcRaw, statusCode, err := vpcPeeringConnectionRefreshState(client.ec2conn, d.Id())()
	// Allow a failed VPC Peering Connection to fallthrough,
	// to allow rest of the logic below to do its work.
	if err != nil && statusCode != ec2.VpcPeeringConnectionStateReasonCodeFailed {
		return errwrap.Wrapf("Error reading VPC Peering Connection: {{err}}", err)
	}

	// The failed status is a status that we can assume just means the
	// connection is gone. Destruction isn't allowed, and it eventually
	// just "falls off" the console. See GH-2322
	status := map[string]bool{
		ec2.VpcPeeringConnectionStateReasonCodeDeleted:  true,
		ec2.VpcPeeringConnectionStateReasonCodeDeleting: true,
		ec2.VpcPeeringConnectionStateReasonCodeExpired:  true,
		ec2.VpcPeeringConnectionStateReasonCodeFailed:   true,
		ec2.VpcPeeringConnectionStateReasonCodeRejected: true,
	}
	if _, ok := status[statusCode]; ok {
		log.Printf("[WARN] VPC Peering Connection (%s) has status code %s, removing from state", d.Id(), statusCode)
		d.SetId("")
		return nil
	}

	pc := pcRaw.(*ec2.VpcPeeringConnection)
	log.Printf("[DEBUG] VPC Peering Connection response: %#v", pc)

	log.Printf("[DEBUG] Account ID %s, VPC PeerConn Requester %s, Accepter %s",
		client.accountid, *pc.RequesterVpcInfo.OwnerId, *pc.AccepterVpcInfo.OwnerId)

	if (client.accountid == *pc.AccepterVpcInfo.OwnerId) && (client.accountid != *pc.RequesterVpcInfo.OwnerId) {
		// We're the accepter
		d.Set("peer_owner_id", pc.RequesterVpcInfo.OwnerId)
		d.Set("peer_vpc_id", pc.RequesterVpcInfo.VpcId)
		d.Set("vpc_id", pc.AccepterVpcInfo.VpcId)
	} else {
		// We're the requester
		d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId)
		d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId)
		d.Set("vpc_id", pc.RequesterVpcInfo.VpcId)
	}

	d.Set("peer_region", pc.AccepterVpcInfo.Region)
	d.Set("accept_status", pc.Status.Code)

	// When the VPC Peering Connection is pending acceptance,
	// the details about accepter and/or requester peering
	// options would not be included in the response.
	if pc.AccepterVpcInfo.PeeringOptions != nil {
		err := d.Set("accepter", flattenVpcPeeringConnectionOptions(pc.AccepterVpcInfo.PeeringOptions))
		if err != nil {
			return errwrap.Wrapf("Error setting VPC Peering Connection accepter information: {{err}}", err)
		}
	}

	if pc.RequesterVpcInfo.PeeringOptions != nil {
		err := d.Set("requester", flattenVpcPeeringConnectionOptions(pc.RequesterVpcInfo.PeeringOptions))
		if err != nil {
			return errwrap.Wrapf("Error setting VPC Peering Connection requester information: {{err}}", err)
		}
	}

	err = d.Set("tags", tagsToMap(pc.Tags))
	if err != nil {
		return errwrap.Wrapf("Error setting VPC Peering Connection tags: {{err}}", err)
	}

	return nil
}

func resourceVPCPeeringConnectionAccept(conn *ec2.EC2, id string) (string, error) {
	log.Printf("[INFO] Accept VPC Peering Connection with ID: %s", id)

	req := &ec2.AcceptVpcPeeringConnectionInput{
		VpcPeeringConnectionId: aws.String(id),
	}

	resp, err := conn.AcceptVpcPeeringConnection(req)
	if err != nil {
		return "", err
	}
	pc := resp.VpcPeeringConnection

	return *pc.Status.Code, nil
}

func resourceAwsVpcPeeringConnectionModifyOptions(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	req := &ec2.ModifyVpcPeeringConnectionOptionsInput{
		VpcPeeringConnectionId: aws.String(d.Id()),
	}

	v := d.Get("accepter").(*schema.Set).List()
	if len(v) > 0 {
		req.AccepterPeeringConnectionOptions = expandVpcPeeringConnectionOptions(v[0].(map[string]interface{}))
	}

	v = d.Get("requester").(*schema.Set).List()
	if len(v) > 0 {
		req.RequesterPeeringConnectionOptions = expandVpcPeeringConnectionOptions(v[0].(map[string]interface{}))
	}

	log.Printf("[DEBUG] Modifying VPC Peering Connection options: %#v", req)
	if _, err := conn.ModifyVpcPeeringConnectionOptions(req); err != nil {
		return err
	}

	return nil
}

func resourceAwsVPCPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	if err := setTags(conn, d); err != nil {
		return err
	} else {
		d.SetPartial("tags")
	}

	pcRaw, _, err := vpcPeeringConnectionRefreshState(conn, d.Id())()
	if err != nil {
		return errwrap.Wrapf("Error reading VPC Peering Connection: {{err}}", err)
	}

	if pcRaw == nil {
		log.Printf("[WARN] VPC Peering Connection (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	pc := pcRaw.(*ec2.VpcPeeringConnection)

	if _, ok := d.GetOk("auto_accept"); ok {
		if pc.Status != nil && *pc.Status.Code == ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance {
			status, err := resourceVPCPeeringConnectionAccept(conn, d.Id())
			if err != nil {
				return errwrap.Wrapf("Unable to accept VPC Peering Connection: {{err}}", err)
			}
			log.Printf("[DEBUG] VPC Peering Connection accept status: %s", status)
		}
	}

	if d.HasChange("accepter") || d.HasChange("requester") {
		_, ok := d.GetOk("auto_accept")
		if !ok && pc.Status != nil && *pc.Status.Code != "active" {
			return fmt.Errorf("Unable to modify peering options. The VPC Peering Connection "+
				"%q is not active. Please set `auto_accept` attribute to `true`, "+
				"or activate VPC Peering Connection manually.", d.Id())
		}

		if err := resourceAwsVpcPeeringConnectionModifyOptions(d, meta); err != nil {
			return errwrap.Wrapf("Error modifying VPC Peering Connection options: {{err}}", err)
		}
	}

	vpcAvailableErr := vpcPeeringConnectionWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate))
	if vpcAvailableErr != nil {
		return errwrap.Wrapf("Error waiting for VPC Peering Connection to become available: {{err}}", vpcAvailableErr)
	}

	return resourceAwsVPCPeeringRead(d, meta)
}

func resourceAwsVPCPeeringDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	input := &ec2.DeleteVpcPeeringConnectionInput{
		VpcPeeringConnectionId: aws.String(d.Id()),
	}
	log.Printf("[DEBUG] Deleting VPC Peering Connection: %s", input)
	_, err := conn.DeleteVpcPeeringConnection(input)
	if err != nil {
		if isAWSErr(err, "InvalidVpcPeeringConnectionID.NotFound", "") {
			return nil
		}
		return fmt.Errorf("Error deleting VPC Peering Connection (%s): %s", d.Id(), err)
	}

	// Wait for the vpc peering connection to delete
	log.Printf("[DEBUG] Waiting for VPC Peering Connection (%s) to delete.", d.Id())
	stateConf := &resource.StateChangeConf{
		Pending: []string{
			ec2.VpcPeeringConnectionStateReasonCodeActive,
			ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance,
			ec2.VpcPeeringConnectionStateReasonCodeDeleting,
		},
		Target: []string{
			ec2.VpcPeeringConnectionStateReasonCodeRejected,
			ec2.VpcPeeringConnectionStateReasonCodeDeleted,
		},
		Refresh: vpcPeeringConnectionRefreshState(conn, d.Id()),
		Timeout: d.Timeout(schema.TimeoutDelete),
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf("Error waiting for VPC Peering Connection (%s) to be deleted: %s", d.Id(), err)
	}

	return nil
}

func vpcPeeringConnectionRefreshState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeVpcPeeringConnections(&ec2.DescribeVpcPeeringConnectionsInput{
			VpcPeeringConnectionIds: aws.StringSlice([]string{id}),
		})
		if err != nil {
			if isNoSuchVpcPeeringConnectionErr(err) {
				return nil, ec2.VpcPeeringConnectionStateReasonCodeDeleted, nil
			}

			return nil, "", err
		}

		pc := resp.VpcPeeringConnections[0]
		statusCode := aws.StringValue(pc.Status.Code)

		// A VPC Peering Connection can exist in a failed state due to
		// incorrect VPC ID, account ID, or overlapping IP address range,
		// thus we short circuit before the time out would occur.
		if statusCode == ec2.VpcPeeringConnectionStateReasonCodeFailed {
			return nil, statusCode, errors.New(aws.StringValue(pc.Status.Message))
		}

		return pc, statusCode, nil
	}
}

func vpcPeeringConnectionOptionsSchema() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeSet,
		Optional: true,
		Computed: true,
		MaxItems: 1,
		Elem: &schema.Resource{
			Schema: map[string]*schema.Schema{
				"allow_remote_vpc_dns_resolution": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
				"allow_classic_link_to_remote_vpc": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
				"allow_vpc_to_remote_classic_link": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
			},
		},
	}
}

func vpcPeeringConnectionWaitUntilAvailable(conn *ec2.EC2, id string, timeout time.Duration) error {
	// Wait for the vpc peering connection to become available
	log.Printf("[DEBUG] Waiting for VPC Peering Connection (%s) to become available.", id)
	stateConf := &resource.StateChangeConf{
		Pending: []string{
			ec2.VpcPeeringConnectionStateReasonCodeInitiatingRequest,
			ec2.VpcPeeringConnectionStateReasonCodeProvisioning,
		},
		Target: []string{
			ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance,
			ec2.VpcPeeringConnectionStateReasonCodeActive,
		},
		Refresh: vpcPeeringConnectionRefreshState(conn, id),
		Timeout: timeout,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return errwrap.Wrapf(fmt.Sprintf(
			"Error waiting for VPC Peering Connection (%s) to become available: {{err}}",
			id), err)
	}
	return nil
}

func isNoSuchVpcPeeringConnectionErr(err error) bool {
	return isAWSErr(err, "InvalidVpcPeeringConnectionID.NotFound", "")
}
<commit_msg>Replace 'errwrap.Wrapf' by 'fmt.Errorf'.<commit_after>package aws

import (
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsVpcPeeringConnection() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsVPCPeeringCreate,
		Read:   resourceAwsVPCPeeringRead,
		Update: resourceAwsVPCPeeringUpdate,
		Delete: resourceAwsVPCPeeringDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(1 * time.Minute),
			Update: schema.DefaultTimeout(1 * time.Minute),
			Delete: schema.DefaultTimeout(1 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"peer_owner_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"peer_vpc_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"vpc_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"auto_accept": {
				Type:     schema.TypeBool,
				Optional: true,
			},
			"accept_status": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"peer_region": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"accepter":  vpcPeeringConnectionOptionsSchema(),
			"requester": vpcPeeringConnectionOptionsSchema(),
			"tags":      tagsSchema(),
		},
	}
}

func resourceAwsVPCPeeringCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	// Create the vpc peering connection
	createOpts := &ec2.CreateVpcPeeringConnectionInput{
		PeerVpcId: aws.String(d.Get("peer_vpc_id").(string)),
		VpcId:     aws.String(d.Get("vpc_id").(string)),
	}

	if v, ok := d.GetOk("peer_owner_id"); ok {
		createOpts.PeerOwnerId = aws.String(v.(string))
	}

	if v, ok := d.GetOk("peer_region"); ok {
		if _, ok := d.GetOk("auto_accept"); ok {
			return fmt.Errorf("peer_region cannot be set whilst auto_accept is true when creating a vpc peering connection")
		}
		createOpts.PeerRegion = aws.String(v.(string))
	}

	log.Printf("[DEBUG] VPC Peering Create options: %#v", createOpts)

	resp, err := conn.CreateVpcPeeringConnection(createOpts)
	if err != nil {
		return fmt.Errorf("Error creating VPC Peering Connection: %s", err)
	}

	// Get the ID and store it
	rt := resp.VpcPeeringConnection
	d.SetId(*rt.VpcPeeringConnectionId)
	log.Printf("[INFO] VPC Peering Connection ID: %s", d.Id())

	err = vpcPeeringConnectionWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate))
	if err != nil {
		return fmt.Errorf("Error waiting for VPC Peering Connection to become available: %s", err)
	}

	return resourceAwsVPCPeeringUpdate(d, meta)
}

func resourceAwsVPCPeeringRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AWSClient)

	pcRaw, statusCode, err := vpcPeeringConnectionRefreshState(client.ec2conn, d.Id())()
	// Allow a failed VPC Peering Connection to fallthrough,
	// to allow rest of the logic below to do its work.
	if err != nil && statusCode != ec2.VpcPeeringConnectionStateReasonCodeFailed {
		return fmt.Errorf("Error reading VPC Peering Connection: %s", err)
	}

	// The failed status is a status that we can assume just means the
	// connection is gone. Destruction isn't allowed, and it eventually
	// just "falls off" the console. See GH-2322
	status := map[string]bool{
		ec2.VpcPeeringConnectionStateReasonCodeDeleted:  true,
		ec2.VpcPeeringConnectionStateReasonCodeDeleting: true,
		ec2.VpcPeeringConnectionStateReasonCodeExpired:  true,
		ec2.VpcPeeringConnectionStateReasonCodeFailed:   true,
		ec2.VpcPeeringConnectionStateReasonCodeRejected: true,
	}
	if _, ok := status[statusCode]; ok {
		log.Printf("[WARN] VPC Peering Connection (%s) has status code %s, removing from state", d.Id(), statusCode)
		d.SetId("")
		return nil
	}

	pc := pcRaw.(*ec2.VpcPeeringConnection)
	log.Printf("[DEBUG] VPC Peering Connection response: %#v", pc)

	log.Printf("[DEBUG] Account ID %s, VPC PeerConn Requester %s, Accepter %s",
		client.accountid, *pc.RequesterVpcInfo.OwnerId, *pc.AccepterVpcInfo.OwnerId)

	if (client.accountid == *pc.AccepterVpcInfo.OwnerId) && (client.accountid != *pc.RequesterVpcInfo.OwnerId) {
		// We're the accepter
		d.Set("peer_owner_id", pc.RequesterVpcInfo.OwnerId)
		d.Set("peer_vpc_id", pc.RequesterVpcInfo.VpcId)
		d.Set("vpc_id", pc.AccepterVpcInfo.VpcId)
	} else {
		// We're the requester
		d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId)
		d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId)
		d.Set("vpc_id", pc.RequesterVpcInfo.VpcId)
	}

	d.Set("peer_region", pc.AccepterVpcInfo.Region)
	d.Set("accept_status", pc.Status.Code)

	// When the VPC Peering Connection is pending acceptance,
	// the details about accepter and/or requester peering
	// options would not be included in the response.
	if pc.AccepterVpcInfo.PeeringOptions != nil {
		err := d.Set("accepter", flattenVpcPeeringConnectionOptions(pc.AccepterVpcInfo.PeeringOptions))
		if err != nil {
			return fmt.Errorf("Error setting VPC Peering Connection accepter information: %s", err)
		}
	}

	if pc.RequesterVpcInfo.PeeringOptions != nil {
		err := d.Set("requester", flattenVpcPeeringConnectionOptions(pc.RequesterVpcInfo.PeeringOptions))
		if err != nil {
			return fmt.Errorf("Error setting VPC Peering Connection requester information: %s", err)
		}
	}

	err = d.Set("tags", tagsToMap(pc.Tags))
	if err != nil {
		return fmt.Errorf("Error setting VPC Peering Connection tags: %s", err)
	}

	return nil
}

func resourceVPCPeeringConnectionAccept(conn *ec2.EC2, id string) (string, error) {
	log.Printf("[INFO] Accept VPC Peering Connection with ID: %s", id)

	req := &ec2.AcceptVpcPeeringConnectionInput{
		VpcPeeringConnectionId: aws.String(id),
	}

	resp, err := conn.AcceptVpcPeeringConnection(req)
	if err != nil {
		return "", err
	}
	pc := resp.VpcPeeringConnection

	return *pc.Status.Code, nil
}

func resourceAwsVpcPeeringConnectionModifyOptions(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	req := &ec2.ModifyVpcPeeringConnectionOptionsInput{
		VpcPeeringConnectionId: aws.String(d.Id()),
	}

	v := d.Get("accepter").(*schema.Set).List()
	if len(v) > 0 {
		req.AccepterPeeringConnectionOptions = expandVpcPeeringConnectionOptions(v[0].(map[string]interface{}))
	}

	v = d.Get("requester").(*schema.Set).List()
	if len(v) > 0 {
		req.RequesterPeeringConnectionOptions = expandVpcPeeringConnectionOptions(v[0].(map[string]interface{}))
	}

	log.Printf("[DEBUG] Modifying VPC Peering Connection options: %#v", req)
	if _, err := conn.ModifyVpcPeeringConnectionOptions(req); err != nil {
		return err
	}

	return nil
}

func resourceAwsVPCPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	if err := setTags(conn, d); err != nil {
		return err
	} else {
		d.SetPartial("tags")
	}

	pcRaw, _, err := vpcPeeringConnectionRefreshState(conn, d.Id())()
	if err != nil {
		return fmt.Errorf("Error reading VPC Peering Connection: %s", err)
	}

	if pcRaw == nil {
		log.Printf("[WARN] VPC Peering Connection (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	pc := pcRaw.(*ec2.VpcPeeringConnection)

	if _, ok := d.GetOk("auto_accept"); ok {
		if pc.Status != nil && *pc.Status.Code == ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance {
			status, err := resourceVPCPeeringConnectionAccept(conn, d.Id())
			if err != nil {
				return fmt.Errorf("Unable to accept VPC Peering Connection: %s", err)
			}
			log.Printf("[DEBUG] VPC Peering Connection accept status: %s", status)
		}
	}

	if d.HasChange("accepter") || d.HasChange("requester") {
		_, ok := d.GetOk("auto_accept")
		if !ok && pc.Status != nil && *pc.Status.Code != "active" {
			return fmt.Errorf("Unable to modify peering options. The VPC Peering Connection "+
				"%q is not active. Please set `auto_accept` attribute to `true`, "+
				"or activate VPC Peering Connection manually.", d.Id())
		}

		if err := resourceAwsVpcPeeringConnectionModifyOptions(d, meta); err != nil {
			return fmt.Errorf("Error modifying VPC Peering Connection options: %s", err)
		}
	}

	err = vpcPeeringConnectionWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate))
	if err != nil {
		return fmt.Errorf("Error waiting for VPC Peering Connection to become available: %s", err)
	}

	return resourceAwsVPCPeeringRead(d, meta)
}

func resourceAwsVPCPeeringDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	req := &ec2.DeleteVpcPeeringConnectionInput{
		VpcPeeringConnectionId: aws.String(d.Id()),
	}
	log.Printf("[DEBUG] Deleting VPC Peering Connection: %s", req)
	_, err := conn.DeleteVpcPeeringConnection(req)
	if err != nil {
		if isAWSErr(err, "InvalidVpcPeeringConnectionID.NotFound", "") {
			return nil
		}
		return fmt.Errorf("Error deleting VPC Peering Connection (%s): %s", d.Id(), err)
	}

	// Wait for the vpc peering connection to delete
	log.Printf("[DEBUG] Waiting for VPC Peering Connection (%s) to delete.", d.Id())
	stateConf := &resource.StateChangeConf{
		Pending: []string{
			ec2.VpcPeeringConnectionStateReasonCodeActive,
			ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance,
			ec2.VpcPeeringConnectionStateReasonCodeDeleting,
		},
		Target: []string{
			ec2.VpcPeeringConnectionStateReasonCodeRejected,
			ec2.VpcPeeringConnectionStateReasonCodeDeleted,
		},
		Refresh: vpcPeeringConnectionRefreshState(conn, d.Id()),
		Timeout: d.Timeout(schema.TimeoutDelete),
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf("Error waiting for VPC Peering Connection (%s) to be deleted: %s", d.Id(), err)
	}

	return nil
}

func vpcPeeringConnectionRefreshState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeVpcPeeringConnections(&ec2.DescribeVpcPeeringConnectionsInput{
			VpcPeeringConnectionIds: aws.StringSlice([]string{id}),
		})
		if err != nil {
			if isNoSuchVpcPeeringConnectionErr(err) {
				return nil, ec2.VpcPeeringConnectionStateReasonCodeDeleted, nil
			}

			return nil, "", err
		}

		pc := resp.VpcPeeringConnections[0]
		statusCode := aws.StringValue(pc.Status.Code)

		// A VPC Peering Connection can exist in a failed state due to
		// incorrect VPC ID, account ID, or overlapping IP address range,
		// thus we short circuit before the time out would occur.
		if statusCode == ec2.VpcPeeringConnectionStateReasonCodeFailed {
			return nil, statusCode, errors.New(aws.StringValue(pc.Status.Message))
		}

		return pc, statusCode, nil
	}
}

func vpcPeeringConnectionOptionsSchema() *schema.Schema {
	return &schema.Schema{
		Type:     schema.TypeSet,
		Optional: true,
		Computed: true,
		MaxItems: 1,
		Elem: &schema.Resource{
			Schema: map[string]*schema.Schema{
				"allow_remote_vpc_dns_resolution": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
				"allow_classic_link_to_remote_vpc": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
				"allow_vpc_to_remote_classic_link": {
					Type:     schema.TypeBool,
					Optional: true,
					Default:  false,
				},
			},
		},
	}
}

func vpcPeeringConnectionWaitUntilAvailable(conn *ec2.EC2, id string, timeout time.Duration) error {
	// Wait for the vpc peering connection to become available
	log.Printf("[DEBUG] Waiting for VPC Peering Connection (%s) to become available.", id)
	stateConf := &resource.StateChangeConf{
		Pending: []string{
			ec2.VpcPeeringConnectionStateReasonCodeInitiatingRequest,
			ec2.VpcPeeringConnectionStateReasonCodeProvisioning,
		},
		Target: []string{
			ec2.VpcPeeringConnectionStateReasonCodePendingAcceptance,
			ec2.VpcPeeringConnectionStateReasonCodeActive,
		},
		Refresh: vpcPeeringConnectionRefreshState(conn, id),
		Timeout: timeout,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf("Error waiting for VPC Peering Connection (%s) to become available: %s", id, err)
	}
	return nil
}

func isNoSuchVpcPeeringConnectionErr(err error) bool {
	return isAWSErr(err, "InvalidVpcPeeringConnectionID.NotFound", "")
}
<|endoftext|>


<commit_before>// Command pubsub is an example of a fanout exchange with dynamic reliable
// membership, reading from stdin, writing to stdout.
//
// This example shows how to implement reconnect logic independent from a
// publish/subscribe loop with bridges to application types.

package main

import (
	"bufio"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/streadway/amqp"
	"golang.org/x/net/context"
)

var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber")

// exchange binds the publishers to the subscribers
const exchange = "pubsub"

// message is the application type for a message.  This can contain identity,
// or a reference to the recevier chan for further demuxing.
type message []byte

// session composes an amqp.Connection with an amqp.Channel
type session struct {
	*amqp.Connection
	*amqp.Channel
}

// Close tears the connection down, taking the channel with it.
func (s session) Close() error {
	if s.Connection == nil {
		return nil
	}
	return s.Connection.Close()
}

// redial continually connects to the URL, exiting the program when no longer possible
func redial(ctx context.Context, url string) chan chan session {
	sessions := make(chan chan session)

	go func() {
		sess := make(chan session)
		defer close(sessions)

		for {
			select {
			case sessions <- sess:
			case <-ctx.Done():
				log.Println("shutting down session factory")
				return
			}

			conn, err := amqp.Dial(url)
			if err != nil {
				log.Fatalf("cannot (re)dial: %v: %q", err, url)
			}

			ch, err := conn.Channel()
			if err != nil {
				log.Fatalf("cannot create channel: %v", err)
			}

			if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil {
				log.Fatalf("cannot declare fanout exchange: %v", err)
			}

			select {
			case sess <- session{conn, ch}:
			case <-ctx.Done():
				log.Println("shutting down new session")
				return
			}
		}
	}()

	return sessions
}

// publish publishes messages to a reconnecting session to a fanout exchange.
// It receives from the application specific source of messages.
func publish(sessions chan chan session, messages <-chan message) {
	var (
		running bool
		reading = messages
		pending = make(chan message, 1)
		confirm = make(amqp.Confirmation, 1)
	)

	for session := range sessions {
		pub := <-session

		// publisher confirms for this channel/connection
		if err := pub.Confirm(false); err != nil {
			log.Printf("publisher confirms not supported")
			close(confirm) // confirms not supported, simulate by always nacking
		} else {
			pub.NotifyPublish(confirm)
		}

		log.Printf("publishing...")

		for {
			var body message
			select {
			case confirmed := <-confirm:
				if !confirmed.Ack {
					log.Printf("nack message %d, body: %q", confirmed.DeliveryTag, string(body))
				}
				reading = messages

			case body = <-pending:
				routingKey := "ignored for fanout exchanges, application dependent for other exchanges"
				err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{
					Body: body,
				})
				// Retry failed delivery on the next session
				if err != nil {
					pending <- body
					pub.Close()
					break
				}

			case body, running = <-reading:
				// all messages consumed
				if !running {
					return
				}
				// work on pending delivery until ack'd
				pending <- body
				reading = nil
			}
		}
	}
}

// identity returns the same host/process unique string for the lifetime of
// this process so that subscriber reconnections reuse the same queue name.
func identity() string {
	hostname, err := os.Hostname()
	h := sha1.New()
	fmt.Fprint(h, hostname)
	fmt.Fprint(h, err)
	fmt.Fprint(h, os.Getpid())
	return fmt.Sprintf("%x", h.Sum(nil))
}

// subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan.
func subscribe(sessions chan chan session, messages chan<- message) {
	queue := identity()

	for session := range sessions {
		sub := <-session

		if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil {
			log.Printf("cannot consume from exclusive queue: %q, %v", queue, err)
			return
		}

		routingKey := "application specific routing key for fancy toplogies"
		if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil {
			log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err)
			return
		}

		deliveries, err := sub.Consume(queue, "", false, true, false, false, nil)
		if err != nil {
			log.Printf("cannot consume from: %q, %v", queue, err)
			return
		}

		log.Printf("subscribed...")

		for msg := range deliveries {
			messages <- message(msg.Body)
			sub.Ack(msg.DeliveryTag, false)
		}
	}
}

// read is this application's translation to the message format, scanning from
// stdin.
func read(r io.Reader) <-chan message {
	lines := make(chan message)
	go func() {
		defer close(lines)
		scan := bufio.NewScanner(r)
		for scan.Scan() {
			lines <- message(scan.Bytes())
		}
	}()
	return lines
}

// write is this application's subscriber of application messages, printing to
// stdout.
func write(w io.Writer) chan<- message {
	lines := make(chan message)
	go func() {
		for line := range lines {
			fmt.Fprintln(w, string(line))
		}
	}()
	return lines
}

func main() {
	flag.Parse()

	ctx, done := context.WithCancel(context.Background())

	go func() {
		publish(redial(ctx, *url), read(os.Stdin))
		done()
	}()

	go func() {
		subscribe(redial(ctx, *url), write(os.Stdout))
		done()
	}()

	<-ctx.Done()
}
<commit_msg>change compile error<commit_after>// Command pubsub is an example of a fanout exchange with dynamic reliable
// membership, reading from stdin, writing to stdout.
//
// This example shows how to implement reconnect logic independent from a
// publish/subscribe loop with bridges to application types.

package main

import (
	"bufio"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/streadway/amqp"
	"golang.org/x/net/context"
)

var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber")

// exchange binds the publishers to the subscribers
const exchange = "pubsub"

// message is the application type for a message.  This can contain identity,
// or a reference to the recevier chan for further demuxing.
type message []byte

// session composes an amqp.Connection with an amqp.Channel
type session struct {
	*amqp.Connection
	*amqp.Channel
}

// Close tears the connection down, taking the channel with it.
func (s session) Close() error {
	if s.Connection == nil {
		return nil
	}
	return s.Connection.Close()
}

// redial continually connects to the URL, exiting the program when no longer possible
func redial(ctx context.Context, url string) chan chan session {
	sessions := make(chan chan session)

	go func() {
		sess := make(chan session)
		defer close(sessions)

		for {
			select {
			case sessions <- sess:
			case <-ctx.Done():
				log.Println("shutting down session factory")
				return
			}

			conn, err := amqp.Dial(url)
			if err != nil {
				log.Fatalf("cannot (re)dial: %v: %q", err, url)
			}

			ch, err := conn.Channel()
			if err != nil {
				log.Fatalf("cannot create channel: %v", err)
			}

			if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil {
				log.Fatalf("cannot declare fanout exchange: %v", err)
			}

			select {
			case sess <- session{conn, ch}:
			case <-ctx.Done():
				log.Println("shutting down new session")
				return
			}
		}
	}()

	return sessions
}

// publish publishes messages to a reconnecting session to a fanout exchange.
// It receives from the application specific source of messages.
func publish(sessions chan chan session, messages <-chan message) {
	var (
		running bool
		reading = messages
		pending = make(chan message, 1)
		confirm = make(chan amqp.Confirmation, 1)
	)

	for session := range sessions {
		pub := <-session

		// publisher confirms for this channel/connection
		if err := pub.Confirm(false); err != nil {
			log.Printf("publisher confirms not supported")
			close(confirm) // confirms not supported, simulate by always nacking
		} else {
			pub.NotifyPublish(confirm)
		}

		log.Printf("publishing...")

		for {
			var body message
			select {
			case confirmed := <-confirm:
				if !confirmed.Ack {
					log.Printf("nack message %d, body: %q", confirmed.DeliveryTag, string(body))
				}
				reading = messages

			case body = <-pending:
				routingKey := "ignored for fanout exchanges, application dependent for other exchanges"
				err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{
					Body: body,
				})
				// Retry failed delivery on the next session
				if err != nil {
					pending <- body
					pub.Close()
					break
				}

			case body, running = <-reading:
				// all messages consumed
				if !running {
					return
				}
				// work on pending delivery until ack'd
				pending <- body
				reading = nil
			}
		}
	}
}

// identity returns the same host/process unique string for the lifetime of
// this process so that subscriber reconnections reuse the same queue name.
func identity() string {
	hostname, err := os.Hostname()
	h := sha1.New()
	fmt.Fprint(h, hostname)
	fmt.Fprint(h, err)
	fmt.Fprint(h, os.Getpid())
	return fmt.Sprintf("%x", h.Sum(nil))
}

// subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan.
func subscribe(sessions chan chan session, messages chan<- message) {
	queue := identity()

	for session := range sessions {
		sub := <-session

		if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil {
			log.Printf("cannot consume from exclusive queue: %q, %v", queue, err)
			return
		}

		routingKey := "application specific routing key for fancy toplogies"
		if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil {
			log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err)
			return
		}

		deliveries, err := sub.Consume(queue, "", false, true, false, false, nil)
		if err != nil {
			log.Printf("cannot consume from: %q, %v", queue, err)
			return
		}

		log.Printf("subscribed...")

		for msg := range deliveries {
			messages <- message(msg.Body)
			sub.Ack(msg.DeliveryTag, false)
		}
	}
}

// read is this application's translation to the message format, scanning from
// stdin.
func read(r io.Reader) <-chan message {
	lines := make(chan message)
	go func() {
		defer close(lines)
		scan := bufio.NewScanner(r)
		for scan.Scan() {
			lines <- message(scan.Bytes())
		}
	}()
	return lines
}

// write is this application's subscriber of application messages, printing to
// stdout.
func write(w io.Writer) chan<- message {
	lines := make(chan message)
	go func() {
		for line := range lines {
			fmt.Fprintln(w, string(line))
		}
	}()
	return lines
}

func main() {
	flag.Parse()

	ctx, done := context.WithCancel(context.Background())

	go func() {
		publish(redial(ctx, *url), read(os.Stdin))
		done()
	}()

	go func() {
		subscribe(redial(ctx, *url), write(os.Stdout))
		done()
	}()

	<-ctx.Done()
}
<|endoftext|>


<commit_before>// Copyright 2017 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resources_test

import (
	"github.com/juju/errors"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/apiserver/resources"
)

var _ = gc.Suite(&AddPendingResourcesSuite{})

type AddPendingResourcesSuite struct {
	BaseSuite
}

func (s *AddPendingResourcesSuite) TestNoURL(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	s.stub.CheckCall(c, 0, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLUpToDate(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		res1.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLMismatchComplete(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 2
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLMismatchIncomplete(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 2
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	apiRes1.Fingerprint = nil
	apiRes1.Size = 0
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 2
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	expected := charmresource.Resource{
		Meta:        csRes.Meta,
		Origin:      charmresource.OriginStore,
		Revision:    3,
		Fingerprint: res1.Fingerprint,
		Size:        res1.Size,
	}
	s.csClient.ReturnResourceInfo = &expected
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "ResourceInfo", "AddPendingResource")
	s.stub.CheckCall(c, 3, "AddPendingResource", "a-application", "", expected, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLNoRevision(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	res1.Size = 10
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = -1
	apiRes1.Size = 0
	apiRes1.Fingerprint = nil
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 3
	csRes.Size = 10
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestLocalCharm(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	expected := charmresource.Resource{
		Meta:   res1.Meta,
		Origin: charmresource.OriginUpload,
	}
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "local:trusty/spam",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	s.stub.CheckCall(c, 0, "AddPendingResource", "a-application", "", expected, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLUpload(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginUpload
	res1.Revision = 0
	apiRes1.Origin = charmresource.OriginUpload.String()
	apiRes1.Revision = 0
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Origin = charmresource.OriginStore
	csRes.Revision = 3
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

// TODO(ericsnow) Once the CS API has ListResources() implemented:
//func (s *AddPendingResourcesSuite) TestUnknownResource(c *gc.C) {
//	_, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
//	apiRes1.Origin = charmresource.OriginStore.String()
//	facade, err := resources.NewFacade(s.data, s.newCSClient)
//	c.Assert(err, jc.ErrorIsNil)
//
//	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
//		Entity: params.Entity{
//			Tag: "application-a-application",
//		},
//		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
//			URL: "cs:~a-user/trusty/spam-5",
//		},
//		Resources: []params.CharmResource{
//			apiRes1.CharmResource,
//		},
//	})
//	c.Assert(err, jc.ErrorIsNil)
//
//	s.stub.CheckCallNames(c, "newCSClient", "ListResources")
//	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
//		ErrorResult: params.ErrorResult{Error: &params.Error{
//			Message: `charm store resource "spam" not found`,
//			Code:    params.CodeNotFound,
//		}},
//	})
//}

func (s *AddPendingResourcesSuite) TestUnknownResource(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		res1.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestDataStoreError(c *gc.C) {
	_, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	failure := errors.New("<failure>")
	s.stub.SetErrors(failure)
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		ErrorResult: params.ErrorResult{Error: &params.Error{
			Message: `while adding pending resource info for "spam": <failure>`,
		}},
	})
}
<commit_msg>apiserver/resources: Deleted commented out test<commit_after>// Copyright 2017 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resources_test

import (
	"github.com/juju/errors"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/apiserver/resources"
)

var _ = gc.Suite(&AddPendingResourcesSuite{})

type AddPendingResourcesSuite struct {
	BaseSuite
}

func (s *AddPendingResourcesSuite) TestNoURL(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	s.stub.CheckCall(c, 0, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLUpToDate(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		res1.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLMismatchComplete(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 2
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLMismatchIncomplete(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 2
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	apiRes1.Fingerprint = nil
	apiRes1.Size = 0
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 2
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	expected := charmresource.Resource{
		Meta:        csRes.Meta,
		Origin:      charmresource.OriginStore,
		Revision:    3,
		Fingerprint: res1.Fingerprint,
		Size:        res1.Size,
	}
	s.csClient.ReturnResourceInfo = &expected
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "ResourceInfo", "AddPendingResource")
	s.stub.CheckCall(c, 3, "AddPendingResource", "a-application", "", expected, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLNoRevision(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	res1.Size = 10
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = -1
	apiRes1.Size = 0
	apiRes1.Fingerprint = nil
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Revision = 3
	csRes.Size = 10
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestLocalCharm(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	expected := charmresource.Resource{
		Meta:   res1.Meta,
		Origin: charmresource.OriginUpload,
	}
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "local:trusty/spam",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	s.stub.CheckCall(c, 0, "AddPendingResource", "a-application", "", expected, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestWithURLUpload(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginUpload
	res1.Revision = 0
	apiRes1.Origin = charmresource.OriginUpload.String()
	apiRes1.Revision = 0
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	csRes := res1 // a copy
	csRes.Origin = charmresource.OriginStore
	csRes.Revision = 3
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		csRes.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result.Error, gc.IsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestUnknownResource(c *gc.C) {
	res1, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	res1.Origin = charmresource.OriginStore
	res1.Revision = 3
	apiRes1.Origin = charmresource.OriginStore.String()
	apiRes1.Revision = 3
	id1 := "some-unique-ID"
	s.data.ReturnAddPendingResource = id1
	s.csClient.ReturnListResources = [][]charmresource.Resource{{
		res1.Resource,
	}}
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		AddCharmWithAuthorization: params.AddCharmWithAuthorization{
			URL: "cs:~a-user/trusty/spam-5",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "newCSClient", "ListResources", "AddPendingResource")
	s.stub.CheckCall(c, 2, "AddPendingResource", "a-application", "", res1.Resource, nil)
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		PendingIDs: []string{
			id1,
		},
	})
}

func (s *AddPendingResourcesSuite) TestDataStoreError(c *gc.C) {
	_, apiRes1 := newResource(c, "spam", "a-user", "spamspamspam")
	failure := errors.New("<failure>")
	s.stub.SetErrors(failure)
	facade, err := resources.NewFacade(s.data, s.newCSClient)
	c.Assert(err, jc.ErrorIsNil)

	result, err := facade.AddPendingResources(params.AddPendingResourcesArgs{
		Entity: params.Entity{
			Tag: "application-a-application",
		},
		Resources: []params.CharmResource{
			apiRes1.CharmResource,
		},
	})
	c.Assert(err, jc.ErrorIsNil)

	s.stub.CheckCallNames(c, "AddPendingResource")
	c.Check(result, jc.DeepEquals, params.AddPendingResourcesResult{
		ErrorResult: params.ErrorResult{Error: &params.Error{
			Message: `while adding pending resource info for "spam": <failure>`,
		}},
	})
}
<|endoftext|>


<commit_before>package mentalese

import (
	"fmt"
	"strconv"
)

type Term struct {
	TermType             int
	TermValue            string
	TermEntityType		 string
	TermValueRelationSet RelationSet
}

const TermVariable = 1
const TermPredicateAtom = 2
const TermStringConstant = 3
const TermAnonymousVariable = 5
const TermRegExp = 6
const TermRelationSet = 7
const TermId = 8

func NewVariable(name string) Term {
	return Term{ TermType: TermVariable, TermValue: name, TermValueRelationSet: nil}
}

func NewAnonymousVariable() Term {
	return Term{ TermType: TermAnonymousVariable, TermValue: "", TermValueRelationSet: nil}
}

func NewString(value string) Term {
	return Term{ TermType: TermStringConstant, TermValue: value, TermValueRelationSet: nil}
}

func NewPredicateAtom(value string) Term {
	return Term{ TermType: TermPredicateAtom, TermValue: value, TermValueRelationSet: nil}
}

func NewRelationSet(value RelationSet) Term {
	return Term{ TermType: TermRelationSet, TermValue: "", TermValueRelationSet: value}
}

func NewId(id string, entityType string) Term {
	return Term{ TermType: TermId, TermValue: id, TermEntityType: entityType, TermValueRelationSet: nil}
}

func (term Term) IsVariable() bool {
	return term.TermType == TermVariable
}

func (term Term) IsNumber() bool {
	if term.TermType != TermStringConstant {
		return false
	}
	_, err := strconv.Atoi(term.TermValue)
	return err == nil
}

func (term Term) IsString() bool {
	return term.TermType == TermStringConstant
}

func (term Term) IsId() bool {
	return term.TermType == TermId
}

func (term Term) IsRegExp() bool {
	return term.TermType == TermRegExp
}

func (term Term) IsAnonymousVariable() bool {
	return term.TermType == TermAnonymousVariable
}

func (term Term) IsAtom() bool {
	return term.TermType == TermPredicateAtom
}

func (term Term) IsRelationSet() bool {
	return term.TermType == TermRelationSet
}

func (term Term) Equals(otherTerm Term) bool {
	if term.TermType != otherTerm.TermType {
		return false
	}
	if term.TermEntityType != otherTerm.TermEntityType {
		return false
	}
	if term.TermType == TermRelationSet {
		return term.TermValueRelationSet.Equals(otherTerm.TermValueRelationSet)
	} else {
		return term.TermValue == otherTerm.TermValue
	}
}

func (term Term) AsKey() string {
	return fmt.Sprintf("%d/%s/%s", term.TermType, term.TermValue, term.TermEntityType)
}

func (term Term) Copy() Term {
	newTerm := Term{}
	newTerm.TermType = term.TermType
	newTerm.TermValue = term.TermValue
	newTerm.TermEntityType = term.TermEntityType
	if term.IsRelationSet() {
		newTerm.TermValueRelationSet = term.TermValueRelationSet.Copy()
	}
	return newTerm
}

func (term Term) Bind(binding Binding) Term {
	arg := term
	if term.IsVariable() {
		newValue, found := binding[term.TermValue]
		if found {
			arg = newValue
		}
	} else if term.IsRelationSet() {
		arg.TermValueRelationSet = term.TermValueRelationSet.BindSingle(binding)
	}
	return arg
}

// If term is a variable, and occurs in binding, returns its binding
// Otherwise, return term
func (term Term) Resolve(binding Binding) Term {

	resolved := term

	if term.IsVariable() {
		 value, found := binding[term.TermValue]
		 if found {
		 	resolved = value
		 }
	}

	return resolved
}

func (term Term) String() string {

	s := ""

	switch term.TermType {
	case TermVariable:
		s = term.TermValue
	case TermPredicateAtom:
		s = term.TermValue
	case TermStringConstant:
		_, err := strconv.Atoi(term.TermValue)
		if err == nil {
			s = term.TermValue
		} else {
			s = "'" + term.TermValue + "'"
		}
	case TermRegExp:
		s = "/" + term.TermValue + "/"
	case TermAnonymousVariable:
		s = "_"
	case TermRelationSet:
		s = term.TermValueRelationSet.String()
	case TermId:
		s = "`" + term.TermEntityType + ":" + term.TermValue + "`"
	default:
		s = "<unknown>"
	}
	return s
}
<commit_msg>term types as string (for easier debugging)<commit_after>package mentalese

import (
	"fmt"
	"strconv"
)

type Term struct {
	TermType             string
	TermValue            string
	TermEntityType		 string
	TermValueRelationSet RelationSet
}

const TermVariable = "variable"
const TermPredicateAtom = "atom"
const TermStringConstant = "string"
const TermAnonymousVariable = "anonymous"
const TermRegExp = "regexp"
const TermRelationSet = "relation-set"
const TermId = "id"

func NewVariable(name string) Term {
	return Term{ TermType: TermVariable, TermValue: name, TermValueRelationSet: nil}
}

func NewAnonymousVariable() Term {
	return Term{ TermType: TermAnonymousVariable, TermValue: "", TermValueRelationSet: nil}
}

func NewString(value string) Term {
	return Term{ TermType: TermStringConstant, TermValue: value, TermValueRelationSet: nil}
}

func NewPredicateAtom(value string) Term {
	return Term{ TermType: TermPredicateAtom, TermValue: value, TermValueRelationSet: nil}
}

func NewRelationSet(value RelationSet) Term {
	return Term{ TermType: TermRelationSet, TermValue: "", TermValueRelationSet: value}
}

func NewId(id string, entityType string) Term {
	return Term{ TermType: TermId, TermValue: id, TermEntityType: entityType, TermValueRelationSet: nil}
}

func (term Term) IsVariable() bool {
	return term.TermType == TermVariable
}

func (term Term) IsNumber() bool {
	if term.TermType != TermStringConstant {
		return false
	}
	_, err := strconv.Atoi(term.TermValue)
	return err == nil
}

func (term Term) IsString() bool {
	return term.TermType == TermStringConstant
}

func (term Term) IsId() bool {
	return term.TermType == TermId
}

func (term Term) IsRegExp() bool {
	return term.TermType == TermRegExp
}

func (term Term) IsAnonymousVariable() bool {
	return term.TermType == TermAnonymousVariable
}

func (term Term) IsAtom() bool {
	return term.TermType == TermPredicateAtom
}

func (term Term) IsRelationSet() bool {
	return term.TermType == TermRelationSet
}

func (term Term) Equals(otherTerm Term) bool {
	if term.TermType != otherTerm.TermType {
		return false
	}
	if term.TermEntityType != otherTerm.TermEntityType {
		return false
	}
	if term.TermType == TermRelationSet {
		return term.TermValueRelationSet.Equals(otherTerm.TermValueRelationSet)
	} else {
		return term.TermValue == otherTerm.TermValue
	}
}

func (term Term) AsKey() string {
	return fmt.Sprintf("%d/%s/%s", term.TermType, term.TermValue, term.TermEntityType)
}

func (term Term) Copy() Term {
	newTerm := Term{}
	newTerm.TermType = term.TermType
	newTerm.TermValue = term.TermValue
	newTerm.TermEntityType = term.TermEntityType
	if term.IsRelationSet() {
		newTerm.TermValueRelationSet = term.TermValueRelationSet.Copy()
	}
	return newTerm
}

func (term Term) Bind(binding Binding) Term {
	arg := term
	if term.IsVariable() {
		newValue, found := binding[term.TermValue]
		if found {
			arg = newValue
		}
	} else if term.IsRelationSet() {
		arg.TermValueRelationSet = term.TermValueRelationSet.BindSingle(binding)
	}
	return arg
}

// If term is a variable, and occurs in binding, returns its binding
// Otherwise, return term
func (term Term) Resolve(binding Binding) Term {

	resolved := term

	if term.IsVariable() {
		 value, found := binding[term.TermValue]
		 if found {
		 	resolved = value
		 }
	}

	return resolved
}

func (term Term) String() string {

	s := ""

	switch term.TermType {
	case TermVariable:
		s = term.TermValue
	case TermPredicateAtom:
		s = term.TermValue
	case TermStringConstant:
		_, err := strconv.Atoi(term.TermValue)
		if err == nil {
			s = term.TermValue
		} else {
			s = "'" + term.TermValue + "'"
		}
	case TermRegExp:
		s = "/" + term.TermValue + "/"
	case TermAnonymousVariable:
		s = "_"
	case TermRelationSet:
		s = term.TermValueRelationSet.String()
	case TermId:
		s = "`" + term.TermEntityType + ":" + term.TermValue + "`"
	default:
		s = "<unknown>"
	}
	return s
}
<|endoftext|>


<commit_before>package monitor

import (
	"io"
	"sync"

	"golang.org/x/net/context"

	"github.com/TheThingsNetwork/ttn/api"
	"github.com/TheThingsNetwork/ttn/api/gateway"
	"github.com/TheThingsNetwork/ttn/api/router"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/apex/log"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)

// Client is a wrapper around MonitorClient
type Client struct {
	Ctx log.Interface

	client MonitorClient
	conn   *grpc.ClientConn
	addr   string

	once *sync.Once

	gateways map[string]GatewayClient
	mutex    sync.RWMutex
}

// NewClient is a wrapper for NewMonitorClient, initializes
// connection to MonitorServer on monitorAddr with default gRPC options
func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) {
	cl = &Client{
		Ctx:      ctx,
		addr:     monitorAddr,
		gateways: make(map[string]GatewayClient),

		once: &sync.Once{},
	}
	return cl, cl.Open()
}

// Open opens connection to the monitor
func (cl *Client) Open() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.open()
}
func (cl *Client) open() (err error) {
	addr := cl.addr
	ctx := cl.Ctx.WithField("addr", addr)

	defer func() {
		if err != nil {
			ctx.Warn("Failed to open monitor connection")
		} else {
			ctx.Info("Monitor connection opened")
		}
	}()

	ctx.Debug("Opening monitor connection...")

	cl.conn, err = grpc.Dial(addr, append(api.DialOptions, grpc.WithInsecure())...)
	if err != nil {
		ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection to gRPC service")
		return err
	}

	cl.client = NewMonitorClient(cl.conn)
	return nil
}

// Close closes connection to the monitor
func (cl *Client) Close() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.close()
}
func (cl *Client) close() (err error) {
	defer func() {
		if err != nil {
			cl.Ctx.Warn("Failed to close monitor connection")
		} else {
			cl.Ctx.Info("Monitor connection closed")
		}
	}()

	for _, gtw := range cl.gateways {
		ctx := cl.Ctx.WithField("GatewayID", gtw.(*gatewayClient).id)

		ctx.Debug("Closing gateway streams...")
		err = gtw.Close()
		if err != nil {
			ctx.Warn("Failed to close gateway streams")
		}
	}

	cl.Ctx.Debug("Closing monitor connection...")
	err = cl.conn.Close()
	if err != nil {
		return err
	}

	cl.conn = nil
	return nil
}

// Reopen reopens connection to the monitor. It first attempts to close already opened connection
// and then opens a new one. If closing already opened connection fails, Reopen fails too.
func (cl *Client) Reopen() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.reopen()
}
func (cl *Client) reopen() (err error) {
	defer func() {
		if err != nil {
			cl.Ctx.Warn("Failed to reopen monitor connection")
		} else {
			cl.Ctx.Info("Monitor connection reopened")
		}
	}()

	cl.Ctx.Debug("Reopening monitor connection...")

	err = cl.close()
	if err != nil {
		return err
	}
	return cl.open()
}

// IsConnected returns whether connection to the monitor had been established or not
func (cl *Client) IsConnected() bool {
	return cl.client != nil && cl.conn != nil
}

// GatewayClient returns monitor GatewayClient for id and token specified
func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) {
	cl.mutex.RLock()
	gtwCl, ok := cl.gateways[id]
	cl.mutex.RUnlock()
	if !ok {
		cl.mutex.Lock()
		gtwCl = &gatewayClient{
			Ctx: cl.Ctx.WithField("GatewayID", id),

			client: cl,

			id:    id,
			token: token,
		}
		cl.gateways[id] = gtwCl
		cl.mutex.Unlock()
	}
	return gtwCl
}

type gatewayClient struct {
	client *Client

	Ctx log.Interface

	id, token string

	status struct {
		stream Monitor_GatewayStatusClient
		sync.RWMutex
	}

	uplink struct {
		stream Monitor_GatewayUplinkClient
		sync.RWMutex
	}

	downlink struct {
		stream Monitor_GatewayDownlinkClient
		sync.RWMutex
	}
}

// GatewayClient is used as the main client for Gateways to communicate with the monitor
type GatewayClient interface {
	SendStatus(status *gateway.Status) (err error)
	SendUplink(msg *router.UplinkMessage) (err error)
	SendDownlink(msg *router.DownlinkMessage) (err error)
	Close() (err error)
}

// SendStatus sends status to the monitor
func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) {
	cl.status.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.status.stream

	cl.status.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent status to monitor")
		}
	}()

	if stream == nil {
		cl.status.Lock()
		if stream = cl.status.stream; stream == nil {
			stream, err = cl.setupStatus()
			if err != nil {
				cl.status.Unlock()
				return err
			}
		}
		cl.status.Unlock()
	}

	if err = stream.Send(status); err == io.EOF {
		cl.Ctx.Warn("Monitor status stream closed")
		cl.status.Lock()
		if cl.status.stream == stream {
			cl.status.stream = nil
		}
		cl.status.Unlock()
		return nil
	}
	return err
}

// SendUplink sends uplink to the monitor
func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) {
	cl.uplink.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.uplink.stream

	cl.uplink.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent uplink to monitor")
		}
	}()

	if stream == nil {
		cl.uplink.Lock()
		if stream = cl.uplink.stream; stream == nil {
			stream, err = cl.setupUplink()
			if err != nil {
				cl.uplink.Unlock()
				return err
			}
		}
		cl.uplink.Unlock()
	}

	if err = stream.Send(uplink); err == io.EOF {
		cl.Ctx.Warn("Monitor uplink stream closed")
		cl.uplink.Lock()
		if cl.uplink.stream == stream {
			cl.uplink.stream = nil
		}
		cl.uplink.Unlock()
		return nil
	}
	return err
}

// SendUplink sends downlink to the monitor
func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) {
	cl.downlink.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.downlink.stream

	cl.downlink.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent downlink to monitor")
		}
	}()

	if stream == nil {
		cl.downlink.Lock()
		if stream = cl.downlink.stream; stream == nil {
			stream, err = cl.setupDownlink()
			if err != nil {
				cl.downlink.Unlock()
				return err
			}
		}
		cl.downlink.Unlock()
	}

	if err = stream.Send(downlink); err == io.EOF {
		cl.Ctx.Warn("Monitor downlink stream closed")
		cl.downlink.Lock()
		if cl.downlink.stream == stream {
			cl.downlink.stream = nil
		}
		cl.downlink.Unlock()
		return nil
	}
	return err
}

// Close closes all opened monitor streams for the gateway
func (cl *gatewayClient) Close() (err error) {
	wg := &sync.WaitGroup{}

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.status.Lock()

		if cl.status.stream != nil {
			if cerr := cl.closeStatus(); cerr != nil {
				err = cerr
			}
			cl.status.stream = nil
		}
	}()
	defer cl.status.Unlock()

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.uplink.Lock()

		if cl.uplink.stream != nil {
			if cerr := cl.closeUplink(); cerr != nil {
				err = cerr
			}
			cl.uplink.stream = nil
		}
	}()
	defer cl.uplink.Unlock()

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.downlink.Lock()

		if cl.downlink.stream != nil {
			cerr := cl.closeDownlink()
			if cerr != nil {
				err = cerr
			}
			cl.downlink.stream = nil
		}
	}()
	defer cl.downlink.Unlock()

	wg.Wait()
	return err
}

// Context returns monitor connection context for gateway
func (cl *gatewayClient) Context() (monitorContext context.Context) {
	return metadata.NewContext(context.Background(), metadata.Pairs(
		"id", cl.id,
		"token", cl.token,
	))
}

func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) {
	stream, err = cl.client.client.GatewayStatus(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor status stream")

	cl.status.stream = stream
	return stream, nil
}
func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) {
	stream, err = cl.client.client.GatewayUplink(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor uplink stream")

	cl.uplink.stream = stream
	return stream, nil
}
func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) {
	stream, err = cl.client.client.GatewayDownlink(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor downlink stream")

	cl.downlink.stream = stream
	return stream, nil
}

func (cl *gatewayClient) closeStatus() (err error) {
	err = cl.status.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream")
	}
	cl.Ctx.Debug("Closed status stream")

	return err
}
func (cl *gatewayClient) closeUplink() (err error) {
	err = cl.uplink.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream")
	}
	cl.Ctx.Debug("Closed uplink stream")

	return err
}
func (cl *gatewayClient) closeDownlink() (err error) {
	err = cl.downlink.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream")
	}
	cl.Ctx.Debug("Closed downlink stream")

	return err
}
<commit_msg>Stop monitor streams on err<commit_after>package monitor

import (
	"io"
	"sync"

	"golang.org/x/net/context"

	"github.com/TheThingsNetwork/ttn/api"
	"github.com/TheThingsNetwork/ttn/api/gateway"
	"github.com/TheThingsNetwork/ttn/api/router"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/apex/log"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)

// Client is a wrapper around MonitorClient
type Client struct {
	Ctx log.Interface

	client MonitorClient
	conn   *grpc.ClientConn
	addr   string

	once *sync.Once

	gateways map[string]GatewayClient
	mutex    sync.RWMutex
}

// NewClient is a wrapper for NewMonitorClient, initializes
// connection to MonitorServer on monitorAddr with default gRPC options
func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) {
	cl = &Client{
		Ctx:      ctx,
		addr:     monitorAddr,
		gateways: make(map[string]GatewayClient),

		once: &sync.Once{},
	}
	return cl, cl.Open()
}

// Open opens connection to the monitor
func (cl *Client) Open() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.open()
}
func (cl *Client) open() (err error) {
	addr := cl.addr
	ctx := cl.Ctx.WithField("addr", addr)

	defer func() {
		if err != nil {
			ctx.Warn("Failed to open monitor connection")
		} else {
			ctx.Info("Monitor connection opened")
		}
	}()

	ctx.Debug("Opening monitor connection...")

	cl.conn, err = grpc.Dial(addr, append(api.DialOptions, grpc.WithInsecure())...)
	if err != nil {
		ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection to gRPC service")
		return err
	}

	cl.client = NewMonitorClient(cl.conn)
	return nil
}

// Close closes connection to the monitor
func (cl *Client) Close() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.close()
}
func (cl *Client) close() (err error) {
	defer func() {
		if err != nil {
			cl.Ctx.Warn("Failed to close monitor connection")
		} else {
			cl.Ctx.Info("Monitor connection closed")
		}
	}()

	for _, gtw := range cl.gateways {
		ctx := cl.Ctx.WithField("GatewayID", gtw.(*gatewayClient).id)

		ctx.Debug("Closing gateway streams...")
		err = gtw.Close()
		if err != nil {
			ctx.Warn("Failed to close gateway streams")
		}
	}

	cl.Ctx.Debug("Closing monitor connection...")
	err = cl.conn.Close()
	if err != nil {
		return err
	}

	cl.conn = nil
	return nil
}

// Reopen reopens connection to the monitor. It first attempts to close already opened connection
// and then opens a new one. If closing already opened connection fails, Reopen fails too.
func (cl *Client) Reopen() (err error) {
	cl.mutex.Lock()
	defer cl.mutex.Unlock()

	return cl.reopen()
}
func (cl *Client) reopen() (err error) {
	defer func() {
		if err != nil {
			cl.Ctx.Warn("Failed to reopen monitor connection")
		} else {
			cl.Ctx.Info("Monitor connection reopened")
		}
	}()

	cl.Ctx.Debug("Reopening monitor connection...")

	err = cl.close()
	if err != nil {
		return err
	}
	return cl.open()
}

// IsConnected returns whether connection to the monitor had been established or not
func (cl *Client) IsConnected() bool {
	return cl.client != nil && cl.conn != nil
}

// GatewayClient returns monitor GatewayClient for id and token specified
func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) {
	cl.mutex.RLock()
	gtwCl, ok := cl.gateways[id]
	cl.mutex.RUnlock()
	if !ok {
		cl.mutex.Lock()
		gtwCl = &gatewayClient{
			Ctx: cl.Ctx.WithField("GatewayID", id),

			client: cl,

			id:    id,
			token: token,
		}
		cl.gateways[id] = gtwCl
		cl.mutex.Unlock()
	}
	return gtwCl
}

type gatewayClient struct {
	client *Client

	Ctx log.Interface

	id, token string

	status struct {
		stream Monitor_GatewayStatusClient
		sync.RWMutex
	}

	uplink struct {
		stream Monitor_GatewayUplinkClient
		sync.RWMutex
	}

	downlink struct {
		stream Monitor_GatewayDownlinkClient
		sync.RWMutex
	}
}

// GatewayClient is used as the main client for Gateways to communicate with the monitor
type GatewayClient interface {
	SendStatus(status *gateway.Status) (err error)
	SendUplink(msg *router.UplinkMessage) (err error)
	SendDownlink(msg *router.DownlinkMessage) (err error)
	Close() (err error)
}

// SendStatus sends status to the monitor
func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) {
	cl.status.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.status.stream

	cl.status.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent status to monitor")
		}
	}()

	if stream == nil {
		cl.status.Lock()
		if stream = cl.status.stream; stream == nil {
			stream, err = cl.setupStatus()
			if err != nil {
				cl.status.Unlock()
				return err
			}
		}
		go func() {
			var msg []byte
			if err := stream.RecvMsg(&msg); err != nil {
				cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...")
				cl.status.Lock()
				cl.status.stream.CloseSend()
				if cl.status.stream == stream {
					cl.status.stream = nil
				}
				cl.status.Unlock()
			}
		}()
		cl.status.Unlock()
	}

	if err = stream.Send(status); err == io.EOF {
		cl.Ctx.Warn("Monitor status stream closed")
		cl.status.Lock()
		if cl.status.stream == stream {
			cl.status.stream = nil
		}
		cl.status.Unlock()
		return nil
	}
	return err
}

// SendUplink sends uplink to the monitor
func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) {
	cl.uplink.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.uplink.stream

	cl.uplink.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent uplink to monitor")
		}
	}()

	if stream == nil {
		cl.uplink.Lock()
		if stream = cl.uplink.stream; stream == nil {
			stream, err = cl.setupUplink()
			if err != nil {
				cl.uplink.Unlock()
				return err
			}
		}
		go func() {
			var msg []byte
			if err := stream.RecvMsg(&msg); err != nil {
				cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...")
				cl.uplink.Lock()
				cl.uplink.stream.CloseSend()
				if cl.uplink.stream == stream {
					cl.uplink.stream = nil
				}
				cl.uplink.Unlock()
			}
		}()
		cl.uplink.Unlock()
	}

	if err = stream.Send(uplink); err == io.EOF {
		cl.Ctx.Warn("Monitor uplink stream closed")
		cl.uplink.Lock()
		if cl.uplink.stream == stream {
			cl.uplink.stream = nil
		}
		cl.uplink.Unlock()
		return nil
	}
	return err
}

// SendUplink sends downlink to the monitor
func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) {
	cl.downlink.RLock()
	cl.client.mutex.RLock()

	once := cl.client.once
	stream := cl.downlink.stream

	cl.downlink.RUnlock()
	cl.client.mutex.RUnlock()

	defer func() {
		if err != nil {
			cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor")

			if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal {
				once.Do(func() {
					err = cl.client.Reopen()

					cl.client.mutex.Lock()
					cl.client.once = &sync.Once{}
					cl.client.mutex.Unlock()
				})
			}
		} else {
			cl.Ctx.Debug("Sent downlink to monitor")
		}
	}()

	if stream == nil {
		cl.downlink.Lock()
		if stream = cl.downlink.stream; stream == nil {
			stream, err = cl.setupDownlink()
			if err != nil {
				cl.downlink.Unlock()
				return err
			}
		}
		go func() {
			var msg []byte
			if err := stream.RecvMsg(&msg); err != nil {
				cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...")
				cl.downlink.Lock()
				cl.downlink.stream.CloseSend()
				if cl.downlink.stream == stream {
					cl.downlink.stream = nil
				}
				cl.downlink.Unlock()
			}
		}()
		cl.downlink.Unlock()
	}

	if err = stream.Send(downlink); err == io.EOF {
		cl.Ctx.Warn("Monitor downlink stream closed")
		cl.downlink.Lock()
		if cl.downlink.stream == stream {
			cl.downlink.stream = nil
		}
		cl.downlink.Unlock()
		return nil
	}
	return err
}

// Close closes all opened monitor streams for the gateway
func (cl *gatewayClient) Close() (err error) {
	wg := &sync.WaitGroup{}

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.status.Lock()

		if cl.status.stream != nil {
			if cerr := cl.closeStatus(); cerr != nil {
				err = cerr
			}
			cl.status.stream = nil
		}
	}()
	defer cl.status.Unlock()

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.uplink.Lock()

		if cl.uplink.stream != nil {
			if cerr := cl.closeUplink(); cerr != nil {
				err = cerr
			}
			cl.uplink.stream = nil
		}
	}()
	defer cl.uplink.Unlock()

	wg.Add(1)
	go func() {
		defer wg.Done()

		cl.downlink.Lock()

		if cl.downlink.stream != nil {
			cerr := cl.closeDownlink()
			if cerr != nil {
				err = cerr
			}
			cl.downlink.stream = nil
		}
	}()
	defer cl.downlink.Unlock()

	wg.Wait()
	return err
}

// Context returns monitor connection context for gateway
func (cl *gatewayClient) Context() (monitorContext context.Context) {
	return metadata.NewContext(context.Background(), metadata.Pairs(
		"id", cl.id,
		"token", cl.token,
	))
}

func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) {
	stream, err = cl.client.client.GatewayStatus(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor status stream")

	cl.status.stream = stream
	return stream, nil
}
func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) {
	stream, err = cl.client.client.GatewayUplink(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor uplink stream")

	cl.uplink.stream = stream
	return stream, nil
}
func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) {
	stream, err = cl.client.client.GatewayDownlink(cl.Context())
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream")
		return nil, err
	}
	cl.Ctx.Debug("Opened new monitor downlink stream")

	cl.downlink.stream = stream
	return stream, nil
}

func (cl *gatewayClient) closeStatus() (err error) {
	err = cl.status.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream")
	}
	cl.Ctx.Debug("Closed status stream")

	return err
}
func (cl *gatewayClient) closeUplink() (err error) {
	err = cl.uplink.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream")
	}
	cl.Ctx.Debug("Closed uplink stream")

	return err
}
func (cl *gatewayClient) closeDownlink() (err error) {
	err = cl.downlink.stream.CloseSend()
	if err != nil {
		cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream")
	}
	cl.Ctx.Debug("Closed downlink stream")

	return err
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.0.46"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<commit_msg>functions: 0.0.47 release [skip ci]<commit_after>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.0.47"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.0.80"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<commit_msg>functions: 0.0.81 release [skip ci]<commit_after>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Version of IronFunctions
var Version = "0.0.81"

func handleVersion(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"version": Version})
}
<|endoftext|>