<commit_before>package aws

import (
	"fmt"
	"regexp"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloud9"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSCloud9EnvironmentEc2_basic(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	rNameUpdated := acctest.RandomWithPrefix("tf-acc-test-updated")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestMatchResourceAttr(resourceName, "owner_arn", regexp.MustCompile(`^arn:`)),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rNameUpdated),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestMatchResourceAttr(resourceName, "owner_arn", regexp.MustCompile(`^arn:`)),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_allFields(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	rNameUpdated := acctest.RandomWithPrefix("tf-acc-test-updated")
	description := acctest.RandomWithPrefix("Tf Acc Test")
	uDescription := acctest.RandomWithPrefix("Tf Acc Test Updated")
	userName := acctest.RandomWithPrefix("tf_acc_cloud9_env")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(rName, description, userName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestMatchResourceAttr(resourceName, "owner_arn", regexp.MustCompile(`^arn:`)),
					resource.TestCheckResourceAttr(resourceName, "type", "ec2"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "automatic_stop_time_minutes", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(rNameUpdated, uDescription, userName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestMatchResourceAttr(resourceName, "owner_arn", regexp.MustCompile(`^arn:`)),
					resource.TestCheckResourceAttr(resourceName, "type", "ec2"),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_tags(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags1(rName, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags2(rName, "key1", "value1updated", "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags1(rName, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_disappears(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					testAccCheckAWSCloud9EnvironmentEc2Disappears(&conf),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckAWSCloud9EnvironmentEc2Exists(n string, res *cloud9.Environment) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No Cloud9 Environment EC2 ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).cloud9conn

		out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{aws.String(rs.Primary.ID)},
		})
		if err != nil {
			if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
				return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID)
			}
			return err
		}
		if len(out.Environments) == 0 {
			return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID)
		}
		env := out.Environments[0]

		*res = *env

		return nil
	}
}

func testAccCheckAWSCloud9EnvironmentEc2Disappears(res *cloud9.Environment) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).cloud9conn

		_, err := conn.DeleteEnvironment(&cloud9.DeleteEnvironmentInput{
			EnvironmentId: res.Id,
		})

		if err != nil {
			return err
		}

		input := &cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{res.Id},
		}
		var out *cloud9.DescribeEnvironmentsOutput
		err = resource.Retry(20*time.Minute, func() *resource.RetryError { // Deleting instances can take a long time
			out, err = conn.DescribeEnvironments(input)
			if err != nil {
				if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
					return nil
				}
				if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") {
					return nil
				}
				return resource.NonRetryableError(err)
			}
			if len(out.Environments) == 0 {
				return nil
			}
			return resource.RetryableError(fmt.Errorf("Cloud9 EC2 Environment %q still exists", aws.StringValue(res.Id)))
		})

		return err
	}
}

func testAccCheckAWSCloud9EnvironmentEc2Destroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).cloud9conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_cloud9_environment_ec2" {
			continue
		}

		out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{aws.String(rs.Primary.ID)},
		})
		if err != nil {
			if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
				return nil
			}
			// :'-(
			if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") {
				return nil
			}
			return err
		}
		if len(out.Environments) == 0 {
			return nil
		}

		return fmt.Errorf("Cloud9 Environment EC2 %q still exists.", rs.Primary.ID)
	}
	return nil
}

func testAccPreCheckAWSCloud9(t *testing.T) {
	conn := testAccProvider.Meta().(*AWSClient).cloud9conn

	input := &cloud9.ListEnvironmentsInput{}

	_, err := conn.ListEnvironments(input)

	if testAccPreCheckSkipError(err) {
		t.Skipf("skipping acceptance testing: %s", err)
	}

	if err != nil {
		t.Fatalf("unexpected PreCheck error: %s", err)
	}
}

func testAccAWSCloud9EnvironmentEc2ConfigBase() string {
	return fmt.Sprintf(`
data "aws_availability_zones" "available" {
  # t2.micro instance type is not available in these Availability Zones
  blacklisted_zone_ids = ["usw2-az4"]
  state                = "available"

  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

resource "aws_vpc" "test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_subnet" "test" {
  availability_zone = data.aws_availability_zones.available.names[0]
  cidr_block        = "10.0.0.0/24"
  vpc_id            = aws_vpc.test.id

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_internet_gateway" "test" {
  vpc_id = aws_vpc.test.id

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_route" "test" {
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.test.id
  route_table_id         = aws_vpc.test.main_route_table_id
}
`)
}

func testAccAWSCloud9EnvironmentEc2Config(name string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id
}
`, name)
}

func testAccAWSCloud9EnvironmentEc2AllFieldsConfig(name, description, userName string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  automatic_stop_time_minutes = 60
  description                 = %[2]q
  instance_type               = "t2.micro"
  name                        = %[1]q
  owner_arn                   = aws_iam_user.test.arn
  subnet_id                   = aws_subnet.test.id
}

resource "aws_iam_user" "test" {
  name = %[3]q
}
`, name, description, userName)
}

func testAccAWSCloud9EnvironmentEc2ConfigTags1(name, tagKey1, tagValue1 string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id

  tags = {
    %[2]q = %[3]q
  }
}
`, name, tagKey1, tagValue1)
}

func testAccAWSCloud9EnvironmentEc2ConfigTags2(name, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id

  tags = {
    %[2]q = %[3]q
    %[4]q = %[5]q

  }
}
`, name, tagKey1, tagValue1, tagKey2, tagValue2)
}
<commit_msg>Updates Cloud9 acceptance tests to use ARN testing check functions<commit_after>package aws

import (
	"fmt"
	"regexp"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloud9"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSCloud9EnvironmentEc2_basic(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	rNameUpdated := acctest.RandomWithPrefix("tf-acc-test-updated")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestCheckResourceAttrPair(resourceName, "owner_arn", "data.aws_caller_identity.current", "arn"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rNameUpdated),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestCheckResourceAttrPair(resourceName, "owner_arn", "data.aws_caller_identity.current", "arn"),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_allFields(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	rNameUpdated := acctest.RandomWithPrefix("tf-acc-test-updated")
	description := acctest.RandomWithPrefix("Tf Acc Test")
	uDescription := acctest.RandomWithPrefix("Tf Acc Test Updated")
	userName := acctest.RandomWithPrefix("tf_acc_cloud9_env")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(rName, description, userName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestCheckResourceAttrPair(resourceName, "owner_arn", "aws_iam_user.test", "arn"),
					resource.TestCheckResourceAttr(resourceName, "type", "ec2"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "automatic_stop_time_minutes", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(rNameUpdated, uDescription, userName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"),
					resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloud9", regexp.MustCompile(`environment:.+$`)),
					resource.TestCheckResourceAttrPair(resourceName, "owner_arn", "aws_iam_user.test", "arn"),
					resource.TestCheckResourceAttr(resourceName, "type", "ec2"),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_tags(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags1(rName, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
				),
			},
			{
				ResourceName:            resourceName,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"instance_type", "subnet_id"},
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags2(rName, "key1", "value1updated", "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
			{
				Config: testAccAWSCloud9EnvironmentEc2ConfigTags1(rName, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
		},
	})
}

func TestAccAWSCloud9EnvironmentEc2_disappears(t *testing.T) {
	var conf cloud9.Environment

	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_cloud9_environment_ec2.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCloud9(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCloud9EnvironmentEc2Config(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf),
					testAccCheckAWSCloud9EnvironmentEc2Disappears(&conf),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckAWSCloud9EnvironmentEc2Exists(n string, res *cloud9.Environment) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No Cloud9 Environment EC2 ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).cloud9conn

		out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{aws.String(rs.Primary.ID)},
		})
		if err != nil {
			if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
				return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID)
			}
			return err
		}
		if len(out.Environments) == 0 {
			return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID)
		}
		env := out.Environments[0]

		*res = *env

		return nil
	}
}

func testAccCheckAWSCloud9EnvironmentEc2Disappears(res *cloud9.Environment) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).cloud9conn

		_, err := conn.DeleteEnvironment(&cloud9.DeleteEnvironmentInput{
			EnvironmentId: res.Id,
		})

		if err != nil {
			return err
		}

		input := &cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{res.Id},
		}
		var out *cloud9.DescribeEnvironmentsOutput
		err = resource.Retry(20*time.Minute, func() *resource.RetryError { // Deleting instances can take a long time
			out, err = conn.DescribeEnvironments(input)
			if err != nil {
				if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
					return nil
				}
				if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") {
					return nil
				}
				return resource.NonRetryableError(err)
			}
			if len(out.Environments) == 0 {
				return nil
			}
			return resource.RetryableError(fmt.Errorf("Cloud9 EC2 Environment %q still exists", aws.StringValue(res.Id)))
		})

		return err
	}
}

func testAccCheckAWSCloud9EnvironmentEc2Destroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).cloud9conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_cloud9_environment_ec2" {
			continue
		}

		out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{
			EnvironmentIds: []*string{aws.String(rs.Primary.ID)},
		})
		if err != nil {
			if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") {
				return nil
			}
			// :'-(
			if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") {
				return nil
			}
			return err
		}
		if len(out.Environments) == 0 {
			return nil
		}

		return fmt.Errorf("Cloud9 Environment EC2 %q still exists.", rs.Primary.ID)
	}
	return nil
}

func testAccPreCheckAWSCloud9(t *testing.T) {
	conn := testAccProvider.Meta().(*AWSClient).cloud9conn

	input := &cloud9.ListEnvironmentsInput{}

	_, err := conn.ListEnvironments(input)

	if testAccPreCheckSkipError(err) {
		t.Skipf("skipping acceptance testing: %s", err)
	}

	if err != nil {
		t.Fatalf("unexpected PreCheck error: %s", err)
	}
}

func testAccAWSCloud9EnvironmentEc2ConfigBase() string {
	return fmt.Sprintf(`
data "aws_availability_zones" "available" {
  # t2.micro instance type is not available in these Availability Zones
  blacklisted_zone_ids = ["usw2-az4"]
  state                = "available"

  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

resource "aws_vpc" "test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_subnet" "test" {
  availability_zone = data.aws_availability_zones.available.names[0]
  cidr_block        = "10.0.0.0/24"
  vpc_id            = aws_vpc.test.id

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_internet_gateway" "test" {
  vpc_id = aws_vpc.test.id

  tags = {
    Name = "tf-acc-test-cloud9-environment-ec2"
  }
}

resource "aws_route" "test" {
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.test.id
  route_table_id         = aws_vpc.test.main_route_table_id
}
`)
}

func testAccAWSCloud9EnvironmentEc2Config(name string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id
}

// By default, the Cloud9 environment EC2 is owned by the creator
data "aws_caller_identity" "current" {}
`, name)
}

func testAccAWSCloud9EnvironmentEc2AllFieldsConfig(name, description, userName string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  automatic_stop_time_minutes = 60
  description                 = %[2]q
  instance_type               = "t2.micro"
  name                        = %[1]q
  owner_arn                   = aws_iam_user.test.arn
  subnet_id                   = aws_subnet.test.id
}

resource "aws_iam_user" "test" {
  name = %[3]q
}
`, name, description, userName)
}

func testAccAWSCloud9EnvironmentEc2ConfigTags1(name, tagKey1, tagValue1 string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id

  tags = {
    %[2]q = %[3]q
  }
}
`, name, tagKey1, tagValue1)
}

func testAccAWSCloud9EnvironmentEc2ConfigTags2(name, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
	return testAccAWSCloud9EnvironmentEc2ConfigBase() + fmt.Sprintf(`
resource "aws_cloud9_environment_ec2" "test" {
  depends_on = [aws_route.test]

  instance_type = "t2.micro"
  name          = %[1]q
  subnet_id     = aws_subnet.test.id

  tags = {
    %[2]q = %[3]q
    %[4]q = %[5]q

  }
}
`, name, tagKey1, tagValue1, tagKey2, tagValue2)
}
<|endoftext|>


<commit_before>package azurerm

import (
	"bytes"
	"fmt"
	"log"
	"strings"

	"github.com/Azure/azure-sdk-for-go/arm/trafficmanager"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmTrafficManagerProfile() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmTrafficManagerProfileCreate,
		Read:   resourceArmTrafficManagerProfileRead,
		Update: resourceArmTrafficManagerProfileCreate,
		Delete: resourceArmTrafficManagerProfileDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"resource_group_name": resourceGroupNameDiffSuppressSchema(),

			"profile_status": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(trafficmanager.ProfileStatusEnabled),
					string(trafficmanager.ProfileStatusDisabled),
				}, true),
				DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
			},

			"traffic_routing_method": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(trafficmanager.Performance),
					string(trafficmanager.Weighted),
					string(trafficmanager.Priority),
				}, false),
			},

			"dns_config": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"relative_name": {
							Type:     schema.TypeString,
							ForceNew: true,
							Required: true,
						},
						"ttl": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntBetween(30, 999999),
						},
					},
				},
				Set: resourceAzureRMTrafficManagerDNSConfigHash,
			},

			// inlined from dns_config for ease of use
			"fqdn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"monitor_config": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"protocol": {
							Type:     schema.TypeString,
							Required: true,
							ValidateFunc: validation.StringInSlice([]string{
								"http",
								"https",
							}, false),
						},
						"port": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntBetween(1, 65535),
						},
						"path": {
							Type:     schema.TypeString,
							Required: true,
						},
					},
				},
				Set: resourceAzureRMTrafficManagerMonitorConfigHash,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmTrafficManagerProfileCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	log.Printf("[INFO] preparing arguments for Azure ARM virtual network creation.")

	name := d.Get("name").(string)
	// must be provided in request
	location := "global"
	resGroup := d.Get("resource_group_name").(string)
	tags := d.Get("tags").(map[string]interface{})

	profile := trafficmanager.Profile{
		Name:              &name,
		Location:          &location,
		ProfileProperties: getArmTrafficManagerProfileProperties(d),
		Tags:              expandTags(tags),
	}

	_, err := client.CreateOrUpdate(resGroup, name, profile)
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("Cannot read TrafficManager profile %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmTrafficManagerProfileRead(d, meta)
}

func resourceArmTrafficManagerProfileRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["trafficManagerProfiles"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if utils.ResponseWasNotFound(resp.Response) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Traffic Manager Profile %s: %+v", name, err)
	}

	profile := *resp.ProfileProperties

	// update appropriate values
	d.Set("resource_group_name", resGroup)
	d.Set("name", resp.Name)
	d.Set("profile_status", profile.ProfileStatus)
	d.Set("traffic_routing_method", profile.TrafficRoutingMethod)

	dnsFlat := flattenAzureRMTrafficManagerProfileDNSConfig(profile.DNSConfig)
	d.Set("dns_config", schema.NewSet(resourceAzureRMTrafficManagerDNSConfigHash, dnsFlat))

	// fqdn is actually inside DNSConfig, inlined for simpler reference
	d.Set("fqdn", profile.DNSConfig.Fqdn)

	monitorFlat := flattenAzureRMTrafficManagerProfileMonitorConfig(profile.MonitorConfig)
	d.Set("monitor_config", schema.NewSet(resourceAzureRMTrafficManagerMonitorConfigHash, monitorFlat))

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmTrafficManagerProfileDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["trafficManagerProfiles"]

	resp, err := client.Delete(resGroup, name)
	if err != nil {
		if !utils.ResponseWasNotFound(resp.Response) {
			return err
		}
	}

	return nil
}

func getArmTrafficManagerProfileProperties(d *schema.ResourceData) *trafficmanager.ProfileProperties {
	routingMethod := d.Get("traffic_routing_method").(string)
	props := &trafficmanager.ProfileProperties{
		TrafficRoutingMethod: trafficmanager.TrafficRoutingMethod(routingMethod),
		DNSConfig:            expandArmTrafficManagerDNSConfig(d),
		MonitorConfig:        expandArmTrafficManagerMonitorConfig(d),
	}

	if status, ok := d.GetOk("profile_status"); ok {
		s := status.(string)
		props.ProfileStatus = trafficmanager.ProfileStatus(s)
	}

	return props
}

func expandArmTrafficManagerMonitorConfig(d *schema.ResourceData) *trafficmanager.MonitorConfig {
	monitorSets := d.Get("monitor_config").(*schema.Set).List()
	monitor := monitorSets[0].(map[string]interface{})

	proto := monitor["protocol"].(string)
	port := int64(monitor["port"].(int))
	path := monitor["path"].(string)

	return &trafficmanager.MonitorConfig{
		Protocol: trafficmanager.MonitorProtocol(proto),
		Port:     &port,
		Path:     &path,
	}
}

func expandArmTrafficManagerDNSConfig(d *schema.ResourceData) *trafficmanager.DNSConfig {
	dnsSets := d.Get("dns_config").(*schema.Set).List()
	dns := dnsSets[0].(map[string]interface{})

	name := dns["relative_name"].(string)
	ttl := int64(dns["ttl"].(int))

	return &trafficmanager.DNSConfig{
		RelativeName: &name,
		TTL:          &ttl,
	}
}

func flattenAzureRMTrafficManagerProfileDNSConfig(dns *trafficmanager.DNSConfig) []interface{} {
	result := make(map[string]interface{})

	result["relative_name"] = *dns.RelativeName
	result["ttl"] = int(*dns.TTL)

	return []interface{}{result}
}

func flattenAzureRMTrafficManagerProfileMonitorConfig(cfg *trafficmanager.MonitorConfig) []interface{} {
	result := make(map[string]interface{})

	result["protocol"] = trafficmanager.MonitorProtocol(cfg.Protocol)
	result["port"] = int(*cfg.Port)
	result["path"] = *cfg.Path

	return []interface{}{result}
}

func resourceAzureRMTrafficManagerDNSConfigHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	buf.WriteString(fmt.Sprintf("%s-", m["relative_name"].(string)))
	buf.WriteString(fmt.Sprintf("%d-", m["ttl"].(int)))

	return hashcode.String(buf.String())
}

func resourceAzureRMTrafficManagerMonitorConfigHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["protocol"].(string))))
	buf.WriteString(fmt.Sprintf("%d-", m["port"].(int)))
	buf.WriteString(fmt.Sprintf("%s-", m["path"].(string)))

	return hashcode.String(buf.String())
}
<commit_msg>Fixing the traffic manager profile<commit_after>package azurerm

import (
	"bytes"
	"fmt"
	"log"
	"strings"

	"github.com/Azure/azure-sdk-for-go/arm/trafficmanager"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmTrafficManagerProfile() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmTrafficManagerProfileCreate,
		Read:   resourceArmTrafficManagerProfileRead,
		Update: resourceArmTrafficManagerProfileCreate,
		Delete: resourceArmTrafficManagerProfileDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"resource_group_name": resourceGroupNameDiffSuppressSchema(),

			"profile_status": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(trafficmanager.ProfileStatusEnabled),
					string(trafficmanager.ProfileStatusDisabled),
				}, true),
				DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
			},

			"traffic_routing_method": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(trafficmanager.Performance),
					string(trafficmanager.Weighted),
					string(trafficmanager.Priority),
				}, false),
			},

			"dns_config": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"relative_name": {
							Type:     schema.TypeString,
							ForceNew: true,
							Required: true,
						},
						"ttl": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntBetween(30, 999999),
						},
					},
				},
				Set: resourceAzureRMTrafficManagerDNSConfigHash,
			},

			// inlined from dns_config for ease of use
			"fqdn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"monitor_config": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"protocol": {
							Type:     schema.TypeString,
							Required: true,
							ValidateFunc: validation.StringInSlice([]string{
								"http",
								"https",
							}, false),
						},
						"port": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntBetween(1, 65535),
						},
						"path": {
							Type:     schema.TypeString,
							Required: true,
						},
					},
				},
				Set: resourceAzureRMTrafficManagerMonitorConfigHash,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmTrafficManagerProfileCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	log.Printf("[INFO] preparing arguments for Azure ARM virtual network creation.")

	name := d.Get("name").(string)
	// must be provided in request
	location := "global"
	resGroup := d.Get("resource_group_name").(string)
	tags := d.Get("tags").(map[string]interface{})

	profile := trafficmanager.Profile{
		Name:              &name,
		Location:          &location,
		ProfileProperties: getArmTrafficManagerProfileProperties(d),
		Tags:              expandTags(tags),
	}

	_, err := client.CreateOrUpdate(resGroup, name, profile)
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("Cannot read TrafficManager profile %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmTrafficManagerProfileRead(d, meta)
}

func resourceArmTrafficManagerProfileRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["trafficManagerProfiles"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if utils.ResponseWasNotFound(resp.Response) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Traffic Manager Profile %s: %+v", name, err)
	}

	profile := *resp.ProfileProperties

	// update appropriate values
	d.Set("resource_group_name", resGroup)
	d.Set("name", resp.Name)
	d.Set("profile_status", profile.ProfileStatus)
	d.Set("traffic_routing_method", profile.TrafficRoutingMethod)

	dnsFlat := flattenAzureRMTrafficManagerProfileDNSConfig(profile.DNSConfig)
	d.Set("dns_config", schema.NewSet(resourceAzureRMTrafficManagerDNSConfigHash, dnsFlat))

	// fqdn is actually inside DNSConfig, inlined for simpler reference
	d.Set("fqdn", profile.DNSConfig.Fqdn)

	monitorFlat := flattenAzureRMTrafficManagerProfileMonitorConfig(profile.MonitorConfig)
	d.Set("monitor_config", schema.NewSet(resourceAzureRMTrafficManagerMonitorConfigHash, monitorFlat))

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmTrafficManagerProfileDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).trafficManagerProfilesClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["trafficManagerProfiles"]

	resp, err := client.Delete(resGroup, name)
	if err != nil {
		if !utils.ResponseWasNotFound(resp.Response) {
			return err
		}
	}

	return nil
}

func getArmTrafficManagerProfileProperties(d *schema.ResourceData) *trafficmanager.ProfileProperties {
	routingMethod := d.Get("traffic_routing_method").(string)
	props := &trafficmanager.ProfileProperties{
		TrafficRoutingMethod: trafficmanager.TrafficRoutingMethod(routingMethod),
		DNSConfig:            expandArmTrafficManagerDNSConfig(d),
		MonitorConfig:        expandArmTrafficManagerMonitorConfig(d),
	}

	if status, ok := d.GetOk("profile_status"); ok {
		s := status.(string)
		props.ProfileStatus = trafficmanager.ProfileStatus(s)
	}

	return props
}

func expandArmTrafficManagerMonitorConfig(d *schema.ResourceData) *trafficmanager.MonitorConfig {
	monitorSets := d.Get("monitor_config").(*schema.Set).List()
	monitor := monitorSets[0].(map[string]interface{})

	proto := monitor["protocol"].(string)
	port := int64(monitor["port"].(int))
	path := monitor["path"].(string)

	return &trafficmanager.MonitorConfig{
		Protocol: trafficmanager.MonitorProtocol(proto),
		Port:     &port,
		Path:     &path,
	}
}

func expandArmTrafficManagerDNSConfig(d *schema.ResourceData) *trafficmanager.DNSConfig {
	dnsSets := d.Get("dns_config").(*schema.Set).List()
	dns := dnsSets[0].(map[string]interface{})

	name := dns["relative_name"].(string)
	ttl := int64(dns["ttl"].(int))

	return &trafficmanager.DNSConfig{
		RelativeName: &name,
		TTL:          &ttl,
	}
}

func flattenAzureRMTrafficManagerProfileDNSConfig(dns *trafficmanager.DNSConfig) []interface{} {
	result := make(map[string]interface{})

	result["relative_name"] = *dns.RelativeName
	result["ttl"] = int(*dns.TTL)

	return []interface{}{result}
}

func flattenAzureRMTrafficManagerProfileMonitorConfig(cfg *trafficmanager.MonitorConfig) []interface{} {
	result := make(map[string]interface{})

	result["protocol"] = string(cfg.Protocol)
	result["port"] = int(*cfg.Port)
	result["path"] = *cfg.Path

	return []interface{}{result}
}

func resourceAzureRMTrafficManagerDNSConfigHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	buf.WriteString(fmt.Sprintf("%s-", m["relative_name"].(string)))
	buf.WriteString(fmt.Sprintf("%d-", m["ttl"].(int)))

	return hashcode.String(buf.String())
}

func resourceAzureRMTrafficManagerMonitorConfigHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["protocol"].(string))))
	buf.WriteString(fmt.Sprintf("%d-", m["port"].(int)))
	buf.WriteString(fmt.Sprintf("%s-", m["path"].(string)))

	return hashcode.String(buf.String())
}
<|endoftext|>


<commit_before>// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"strconv"
	"sync"
	"time"

	"github.com/cilium/cilium/common"
	"github.com/cilium/cilium/common/addressing"
	"github.com/cilium/cilium/common/types"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/policy"

	dTypes "github.com/docker/engine-api/types"
	dTypesEvents "github.com/docker/engine-api/types/events"
	ctx "golang.org/x/net/context"
	k8sDockerLbls "k8s.io/client-go/1.5/pkg/kubelet/types"
)

const (
	syncRateDocker = time.Duration(30 * time.Second)

	maxRetries = 3
)

// EnableDockerEventListener watches for docker events. Performs the plumbing for the
// containers started or dead.
func (d *Daemon) EnableDockerEventListener(since time.Time) error {
	eo := dTypes.EventsOptions{Since: strconv.FormatInt(since.Unix(), 10)}
	r, err := d.dockerClient.Events(ctx.Background(), eo)
	if err != nil {
		return err
	}
	log.Debugf("Listening for docker events")
	go d.listenForDockerEvents(r)
	return nil
}

func (d *Daemon) SyncDocker(wg *sync.WaitGroup) {
	cList, err := d.dockerClient.ContainerList(ctx.Background(), dTypes.ContainerListOptions{All: false})
	if err != nil {
		log.Errorf("Failed to retrieve the container list %s", err)
	}
	for _, cont := range cList {
		if d.IgnoredContainer(cont.ID) {
			continue
		}

		wg.Add(1)
		go func(wg *sync.WaitGroup, id string) {
			d.handleCreateContainer(id)
			wg.Done()
		}(wg, cont.ID)
	}
}

func (d *Daemon) EnableDockerSync() {
	var wg sync.WaitGroup
	for {
		d.SyncDocker(&wg)
		wg.Wait()
		time.Sleep(syncRateDocker)
	}
}

func (d *Daemon) listenForDockerEvents(reader io.ReadCloser) {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var e dTypesEvents.Message
		if err := json.Unmarshal(scanner.Bytes(), &e); err != nil {
			log.Errorf("Error while unmarshalling event: %+v", e)
		}
		log.Debugf("Processing an event %+v", e)
		go d.processEvent(e)
	}
	if err := scanner.Err(); err != nil {
		log.Errorf("Error while reading events: %+v", err)
	}
}

func (d *Daemon) processEvent(m dTypesEvents.Message) {
	if m.Type == "container" {
		switch m.Status {
		case "start":
			// A real event overwrites any memory of ignored containers
			d.StopIgnoringContainer(m.ID)
			d.handleCreateContainer(m.ID)
		case "die":
			d.deleteContainer(m.ID)
		}
	}
}

func getCiliumEndpointID(cont *dTypes.ContainerJSON, gwIP *addressing.NodeAddress) uint16 {
	for _, contNetwork := range cont.NetworkSettings.Networks {
		ipv6gw := net.ParseIP(contNetwork.IPv6Gateway)
		if ipv6gw.Equal(gwIP.IPv6Address.IP()) {
			ip, err := addressing.NewCiliumIPv6(contNetwork.GlobalIPv6Address)
			if err == nil {
				return ip.EndpointID()
			}
		}
	}

	return 0
}

func (d *Daemon) fetchK8sLabels(dockerLbls map[string]string) (map[string]string, error) {
	if !d.conf.IsK8sEnabled() {
		return nil, nil
	}
	ns := k8sDockerLbls.GetPodNamespace(dockerLbls)
	if ns == "" {
		ns = "default"
	}
	podName := k8sDockerLbls.GetPodName(dockerLbls)
	if podName == "" {
		return nil, nil
	}
	log.Debugf("Connecting to kubernetes to retrieve labels for pod %s ns %s", podName, ns)

	result, err := d.k8sClient.Pods(ns).Get(podName)
	if err != nil {
		return nil, err
	}
	k8sLabels := result.GetLabels()
	k8sLabels[common.K8sPodNamespaceLabel] = ns
	return k8sLabels, nil
}

func (d *Daemon) getFilteredLabels(allLabels map[string]string) labels.Labels {
	var ciliumLabels, k8sLabels, k8sSpecialLabels labels.Labels
	if podName := k8sDockerLbls.GetPodName(allLabels); podName != "" {
		k8sNormalLabels, err := d.fetchK8sLabels(allLabels)
		if err != nil {
			log.Warningf("Error while getting kubernetes labels: %s", err)
		} else if k8sNormalLabels != nil {
			k8sLabels = labels.Map2Labels(k8sNormalLabels, common.K8sLabelSource)

			// Transform all labels "k8s-app" and "version" to "io.cilium.k8s.k8s-app"
			// and  "io.cilium.k8s.version"
			d.conf.ValidLabelPrefixesMU.RLock()
			k8sSpecialLabels = d.conf.ValidK8sLabelPrefixes.FilterLabels(k8sLabels)
			d.conf.ValidLabelPrefixesMU.RUnlock()
			k8sSpecialLabels = k8sSpecialLabels.AppendPrefixInKey(common.K8sLabelPrefix)
			log.Debug("Special labels %s", k8sSpecialLabels)
		}
	}

	ciliumLabels = labels.Map2Labels(allLabels, common.CiliumLabelSource)

	ciliumLabels.MergeLabels(k8sLabels)

	d.conf.ValidLabelPrefixesMU.RLock()
	normalLabels := d.conf.ValidLabelPrefixes.FilterLabels(ciliumLabels)
	d.conf.ValidLabelPrefixesMU.RUnlock()

	normalLabels.MergeLabels(k8sSpecialLabels)
	return normalLabels
}

func (d *Daemon) retrieveWorkingContainerCopy(id string) (types.Container, bool) {
	d.containersMU.RLock()
	defer d.containersMU.RUnlock()

	if c, ok := d.containers[id]; ok {
		return *c, true
	} else {
		return types.Container{}, false
	}
}

func createContainer(dc *dTypes.ContainerJSON, l labels.Labels) types.Container {
	return types.Container{
		ContainerJSON: *dc,
		OpLabels: labels.OpLabels{
			Custom:        labels.Labels{},
			Disabled:      labels.Labels{},
			Orchestration: l.DeepCopy(),
		},
	}
}

func (d *Daemon) handleCreateContainer(id string) {
	log.Debugf("Processing create event for docker container %s", id)

	maxTries := 5

	for try := 1; try <= maxTries; try++ {
		if try > 1 {
			log.Debugf("Waiting for container %s to appear as endpoint [%d/%d]",
				id, try, maxTries)
			time.Sleep(time.Duration(try) * time.Second)
		}

		dockerContainer, lbls, err := d.retrieveDockerLabels(id)
		if err != nil {
			log.Warningf("unable to inspect container %s, retrying later (%s)", id, err)
			continue
		}

		dockerEpID := ""
		if dockerContainer.NetworkSettings != nil {
			dockerEpID = dockerContainer.NetworkSettings.EndpointID
		}

		d.endpointsMU.Lock()
		ep := d.lookupDockerID(id)
		if ep == nil {
			// container id is yet unknown, try and find endpoint via
			// the IP address assigned.
			cid := getCiliumEndpointID(dockerContainer, d.conf.NodeAddress)
			if cid != 0 {
				ep = d.lookupCiliumEndpoint(cid)
				if ep != nil {
					// Associate container id with endpoint
					ep.DockerID = id
					d.linkContainerID(ep)
				}
			}
		}

		d.endpointsMU.Unlock()
		if ep == nil {
			// Endpoint does not exist yet. This indicates that the
			// orchestration system has not requested us to handle
			// networking for this container yet (or never will). We
			// will retry a couple of times to wait for this to
			// happen.
			continue
		}

		containerCopy, ok := d.retrieveWorkingContainerCopy(id)
		if ok {
			if !updateOrchLabels(&containerCopy, lbls) {
				log.Debugf("No changes to orch labels, ignoring")
				return
			}
		} else {
			containerCopy = createContainer(dockerContainer, lbls)
		}

		identity, err := d.updateContainerIdentity(&containerCopy)
		if err != nil {
			log.Warningf("unable to update identity of container %s: %s", id, err)
			return
		}

		// FIXME:

		d.endpointsMU.Lock()
		ep = d.lookupDockerID(id)
		if ep == nil {
			d.endpointsMU.Unlock()
			log.Warningf("endpoint disappeared while processing event for %s, ignoring", id)
			return
		}

		d.containersMU.Lock()

		// If the container ID was known and found before, check if it still
		// exists, it may have disappared while we gave up the containers
		// lock to create/udpate the identity.
		if ok && d.containers[ep.DockerID] == nil {
			// endpoint is around but container id was removed, likely
			// a bug.
			//
			// FIXME: Disconnect endpoint?
			d.endpointsMU.Unlock()
			d.containersMU.Unlock()
			log.Errorf("BUG: unrefered container %s with endpoint %d present",
				id, ep.ID)
			return
		}

		// Commit label changes to container
		d.containers[ep.DockerID] = &containerCopy

		d.setEndpointIdentity(ep, containerCopy.ID, dockerEpID, identity)
		if err := ep.Regenerate(d); err != nil {
			// FIXME: Disconnect endpoint?
		}

		d.endpointsMU.Unlock()
		d.containersMU.Unlock()

		// FIXME: Does this rebuild epID twice?
		d.triggerPolicyUpdates([]policy.NumericIdentity{identity.ID})
		return
	}

	d.StartIgnoringContainer(id)
	log.Infof("Container %s did not appear as endpoint. Likely managed by other plugin", id)
}

func (d *Daemon) retrieveDockerLabels(dockerID string) (*dTypes.ContainerJSON, labels.Labels, error) {
	dockerCont, err := d.dockerClient.ContainerInspect(ctx.Background(), dockerID)
	if err != nil {
		return nil, nil, fmt.Errorf("unable to inspect container '%s': %s", dockerID, err)
	}

	newLabels := labels.Labels{}
	if dockerCont.Config != nil {
		newLabels = d.getFilteredLabels(dockerCont.Config.Labels)
	}

	return &dockerCont, newLabels, nil
}

func updateOrchLabels(c *types.Container, l labels.Labels) bool {
	changed := false

	c.OpLabels.Orchestration.MarkAllForDeletion()
	c.OpLabels.Disabled.MarkAllForDeletion()

	for k, v := range l {
		if c.OpLabels.Disabled[k] != nil {
			c.OpLabels.Disabled[k].DeletionMark = false
		} else {
			if c.OpLabels.Orchestration[k] != nil {
				c.OpLabels.Orchestration[k].DeletionMark = false
			} else {
				tmp := *v
				log.Debugf("Assigning orchestration label %+v", tmp)
				c.OpLabels.Orchestration[k] = &tmp
				changed = true
			}
		}
	}

	if c.OpLabels.Orchestration.DeleteMarked() || c.OpLabels.Disabled.DeleteMarked() {
		changed = true
	}

	return changed
}

func (d *Daemon) deleteContainer(dockerID string) {
	log.Debugf("Processing deletion event for docker container %s", dockerID)

	d.containersMU.Lock()
	if container, ok := d.containers[dockerID]; ok {
		sha256sum := container.OpLabels.Enabled().SHA256Sum()
		if err := d.DeleteIdentityBySHA256(sha256sum, dockerID); err != nil {
			log.Errorf("Error while deleting labels (SHA256SUM:%s) %+v: %s",
				sha256sum, container.OpLabels.Enabled(), err)
		}

		delete(d.containers, dockerID)
	}
	d.containersMU.Unlock()

	d.DeleteEndpoint(endpoint.NewID(endpoint.ContainerIdPrefix, dockerID))
}
<commit_msg>daemon: fixing lack of identity endpoints update<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"strconv"
	"sync"
	"time"

	"github.com/cilium/cilium/common"
	"github.com/cilium/cilium/common/addressing"
	"github.com/cilium/cilium/common/types"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/policy"

	dTypes "github.com/docker/engine-api/types"
	dTypesEvents "github.com/docker/engine-api/types/events"
	ctx "golang.org/x/net/context"
	k8sDockerLbls "k8s.io/client-go/1.5/pkg/kubelet/types"
)

const (
	syncRateDocker = time.Duration(30 * time.Second)

	maxRetries = 3
)

// EnableDockerEventListener watches for docker events. Performs the plumbing for the
// containers started or dead.
func (d *Daemon) EnableDockerEventListener(since time.Time) error {
	eo := dTypes.EventsOptions{Since: strconv.FormatInt(since.Unix(), 10)}
	r, err := d.dockerClient.Events(ctx.Background(), eo)
	if err != nil {
		return err
	}
	log.Debugf("Listening for docker events")
	go d.listenForDockerEvents(r)
	return nil
}

func (d *Daemon) SyncDocker(wg *sync.WaitGroup) {
	cList, err := d.dockerClient.ContainerList(ctx.Background(), dTypes.ContainerListOptions{All: false})
	if err != nil {
		log.Errorf("Failed to retrieve the container list %s", err)
	}
	for _, cont := range cList {
		if d.IgnoredContainer(cont.ID) {
			continue
		}

		wg.Add(1)
		go func(wg *sync.WaitGroup, id string) {
			d.handleCreateContainer(id)
			wg.Done()
		}(wg, cont.ID)
	}
}

func (d *Daemon) EnableDockerSync() {
	var wg sync.WaitGroup
	for {
		d.SyncDocker(&wg)
		wg.Wait()
		time.Sleep(syncRateDocker)
	}
}

func (d *Daemon) listenForDockerEvents(reader io.ReadCloser) {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		var e dTypesEvents.Message
		if err := json.Unmarshal(scanner.Bytes(), &e); err != nil {
			log.Errorf("Error while unmarshalling event: %+v", e)
		}
		log.Debugf("Processing an event %+v", e)
		go d.processEvent(e)
	}
	if err := scanner.Err(); err != nil {
		log.Errorf("Error while reading events: %+v", err)
	}
}

func (d *Daemon) processEvent(m dTypesEvents.Message) {
	if m.Type == "container" {
		switch m.Status {
		case "start":
			// A real event overwrites any memory of ignored containers
			d.StopIgnoringContainer(m.ID)
			d.handleCreateContainer(m.ID)
		case "die":
			d.deleteContainer(m.ID)
		}
	}
}

func getCiliumEndpointID(cont *dTypes.ContainerJSON, gwIP *addressing.NodeAddress) uint16 {
	for _, contNetwork := range cont.NetworkSettings.Networks {
		ipv6gw := net.ParseIP(contNetwork.IPv6Gateway)
		if ipv6gw.Equal(gwIP.IPv6Address.IP()) {
			ip, err := addressing.NewCiliumIPv6(contNetwork.GlobalIPv6Address)
			if err == nil {
				return ip.EndpointID()
			}
		}
	}

	return 0
}

func (d *Daemon) fetchK8sLabels(dockerLbls map[string]string) (map[string]string, error) {
	if !d.conf.IsK8sEnabled() {
		return nil, nil
	}
	ns := k8sDockerLbls.GetPodNamespace(dockerLbls)
	if ns == "" {
		ns = "default"
	}
	podName := k8sDockerLbls.GetPodName(dockerLbls)
	if podName == "" {
		return nil, nil
	}
	log.Debugf("Connecting to kubernetes to retrieve labels for pod %s ns %s", podName, ns)

	result, err := d.k8sClient.Pods(ns).Get(podName)
	if err != nil {
		return nil, err
	}
	k8sLabels := result.GetLabels()
	k8sLabels[common.K8sPodNamespaceLabel] = ns
	return k8sLabels, nil
}

func (d *Daemon) getFilteredLabels(allLabels map[string]string) labels.Labels {
	var ciliumLabels, k8sLabels, k8sSpecialLabels labels.Labels
	if podName := k8sDockerLbls.GetPodName(allLabels); podName != "" {
		k8sNormalLabels, err := d.fetchK8sLabels(allLabels)
		if err != nil {
			log.Warningf("Error while getting kubernetes labels: %s", err)
		} else if k8sNormalLabels != nil {
			k8sLabels = labels.Map2Labels(k8sNormalLabels, common.K8sLabelSource)

			// Transform all labels "k8s-app" and "version" to "io.cilium.k8s.k8s-app"
			// and  "io.cilium.k8s.version"
			d.conf.ValidLabelPrefixesMU.RLock()
			k8sSpecialLabels = d.conf.ValidK8sLabelPrefixes.FilterLabels(k8sLabels)
			d.conf.ValidLabelPrefixesMU.RUnlock()
			k8sSpecialLabels = k8sSpecialLabels.AppendPrefixInKey(common.K8sLabelPrefix)
			log.Debug("Special labels %s", k8sSpecialLabels)
		}
	}

	ciliumLabels = labels.Map2Labels(allLabels, common.CiliumLabelSource)

	ciliumLabels.MergeLabels(k8sLabels)

	d.conf.ValidLabelPrefixesMU.RLock()
	normalLabels := d.conf.ValidLabelPrefixes.FilterLabels(ciliumLabels)
	d.conf.ValidLabelPrefixesMU.RUnlock()

	normalLabels.MergeLabels(k8sSpecialLabels)
	return normalLabels
}

func (d *Daemon) retrieveWorkingContainerCopy(id string) (types.Container, bool) {
	d.containersMU.RLock()
	defer d.containersMU.RUnlock()

	if c, ok := d.containers[id]; ok {
		return *c, true
	} else {
		return types.Container{}, false
	}
}

func createContainer(dc *dTypes.ContainerJSON, l labels.Labels) types.Container {
	return types.Container{
		ContainerJSON: *dc,
		OpLabels: labels.OpLabels{
			Custom:        labels.Labels{},
			Disabled:      labels.Labels{},
			Orchestration: l.DeepCopy(),
		},
	}
}

func (d *Daemon) handleCreateContainer(id string) {
	log.Debugf("Processing create event for docker container %s", id)

	maxTries := 5

	for try := 1; try <= maxTries; try++ {
		if try > 1 {
			log.Debugf("Waiting for container %s to appear as endpoint [%d/%d]",
				id, try, maxTries)
			time.Sleep(time.Duration(try) * time.Second)
		}

		dockerContainer, lbls, err := d.retrieveDockerLabels(id)
		if err != nil {
			log.Warningf("unable to inspect container %s, retrying later (%s)", id, err)
			continue
		}

		dockerEpID := ""
		if dockerContainer.NetworkSettings != nil {
			dockerEpID = dockerContainer.NetworkSettings.EndpointID
		}

		d.endpointsMU.Lock()
		ep := d.lookupDockerID(id)
		if ep == nil {
			// container id is yet unknown, try and find endpoint via
			// the IP address assigned.
			cid := getCiliumEndpointID(dockerContainer, d.conf.NodeAddress)
			if cid != 0 {
				ep = d.lookupCiliumEndpoint(cid)
				if ep != nil {
					// Associate container id with endpoint
					ep.DockerID = id
					d.linkContainerID(ep)
				}
			}
		}

		d.endpointsMU.Unlock()
		if ep == nil {
			// Endpoint does not exist yet. This indicates that the
			// orchestration system has not requested us to handle
			// networking for this container yet (or never will). We
			// will retry a couple of times to wait for this to
			// happen.
			continue
		}

		var orchLabelsModified bool
		containerCopy, ok := d.retrieveWorkingContainerCopy(id)
		if ok {
			if orchLabelsModified = updateOrchLabels(&containerCopy, lbls); !orchLabelsModified {
				log.Debugf("No changes to orch labels, ignoring")
			}
		} else {
			containerCopy = createContainer(dockerContainer, lbls)
		}

		// It's mandatory to update the container in its label otherwise
		// the label will be considered unused.
		identity, err := d.updateContainerIdentity(&containerCopy)
		if err != nil {
			log.Warningf("unable to update identity of container %s: %s", id, err)
			return
		}

		if ok && !orchLabelsModified {
			return
		}

		// FIXME:

		d.endpointsMU.Lock()
		ep = d.lookupDockerID(id)
		if ep == nil {
			d.endpointsMU.Unlock()
			log.Warningf("endpoint disappeared while processing event for %s, ignoring", id)
			return
		}

		d.containersMU.Lock()

		// If the container ID was known and found before, check if it still
		// exists, it may have disappared while we gave up the containers
		// lock to create/udpate the identity.
		if ok && d.containers[ep.DockerID] == nil {
			// endpoint is around but container id was removed, likely
			// a bug.
			//
			// FIXME: Disconnect endpoint?
			d.endpointsMU.Unlock()
			d.containersMU.Unlock()
			log.Errorf("BUG: unrefered container %s with endpoint %d present",
				id, ep.ID)
			return
		}

		// Commit label changes to container
		d.containers[ep.DockerID] = &containerCopy

		d.setEndpointIdentity(ep, containerCopy.ID, dockerEpID, identity)
		if err := ep.Regenerate(d); err != nil {
			// FIXME: Disconnect endpoint?
		}

		d.endpointsMU.Unlock()
		d.containersMU.Unlock()

		// FIXME: Does this rebuild epID twice?
		d.triggerPolicyUpdates([]policy.NumericIdentity{identity.ID})
		return
	}

	d.StartIgnoringContainer(id)
	log.Infof("Container %s did not appear as endpoint. Likely managed by other plugin", id)
}

func (d *Daemon) retrieveDockerLabels(dockerID string) (*dTypes.ContainerJSON, labels.Labels, error) {
	dockerCont, err := d.dockerClient.ContainerInspect(ctx.Background(), dockerID)
	if err != nil {
		return nil, nil, fmt.Errorf("unable to inspect container '%s': %s", dockerID, err)
	}

	newLabels := labels.Labels{}
	if dockerCont.Config != nil {
		newLabels = d.getFilteredLabels(dockerCont.Config.Labels)
	}

	return &dockerCont, newLabels, nil
}

func updateOrchLabels(c *types.Container, l labels.Labels) bool {
	changed := false

	c.OpLabels.Orchestration.MarkAllForDeletion()
	c.OpLabels.Disabled.MarkAllForDeletion()

	for k, v := range l {
		if c.OpLabels.Disabled[k] != nil {
			c.OpLabels.Disabled[k].DeletionMark = false
		} else {
			if c.OpLabels.Orchestration[k] != nil {
				c.OpLabels.Orchestration[k].DeletionMark = false
			} else {
				tmp := *v
				log.Debugf("Assigning orchestration label %+v", tmp)
				c.OpLabels.Orchestration[k] = &tmp
				changed = true
			}
		}
	}

	if c.OpLabels.Orchestration.DeleteMarked() || c.OpLabels.Disabled.DeleteMarked() {
		changed = true
	}

	return changed
}

func (d *Daemon) deleteContainer(dockerID string) {
	log.Debugf("Processing deletion event for docker container %s", dockerID)

	d.containersMU.Lock()
	if container, ok := d.containers[dockerID]; ok {
		sha256sum := container.OpLabels.Enabled().SHA256Sum()
		if err := d.DeleteIdentityBySHA256(sha256sum, dockerID); err != nil {
			log.Errorf("Error while deleting labels (SHA256SUM:%s) %+v: %s",
				sha256sum, container.OpLabels.Enabled(), err)
		}

		delete(d.containers, dockerID)
	}
	d.containersMU.Unlock()

	d.DeleteEndpoint(endpoint.NewID(endpoint.ContainerIdPrefix, dockerID))
}
<|endoftext|>


<commit_before>package streaming

import (
	"errors"
	. "github.com/byrnedo/apibase/natsio/protobuf"
	"github.com/gogo/protobuf/proto"
	"github.com/nats-io/go-nats-streaming"
	"github.com/pborman/uuid"
	"time"
)

// stan.Options wrapper.
type Stan struct {
	Opts *StanOptions
	Con  stan.Conn
}

// Subscribe and record subscription to routes
func (n *Stan) Subscribe(route string, handler stan.MsgHandler, opts ...stan.SubscriptionOption) error {
	subsc, err := n.Con.Subscribe(route, handler, opts...)
	if err != nil {
		return errors.New("Failed to make subcriptions for " + route + ": " + err.Error())
	}
	n.Opts.routes = append(n.Opts.routes, &Route{route: route, handler: handler, subsc: subsc})
	return nil
}

// Subscribe to queue group and record subscription to routes
func (n *Stan) QueueSubscribe(route string, group string, handler stan.MsgHandler, opts ...stan.SubscriptionOption) error {
	subsc, err := n.Con.QueueSubscribe(route, group, handler, opts...)
	if err != nil {
		return errors.New("Failed to make subcriptions for " + route + ": " + err.Error())
	}
	n.Opts.routes = append(n.Opts.routes, &Route{route: route, handler: handler, subsc: subsc, queueGroup: group})
	return nil
}

// For use when using nats request/publish/publishrequest wrapper functions
type PayloadWithContext interface {
	proto.Message
	GetContext() *NatsContext
	SetContext(*NatsContext)
}

// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) updateContext(data PayloadWithContext, requestType RequestType) {
	var ctx *NatsContext

	if data.GetContext() == nil {
		data.SetContext(&NatsContext{})
	}

	ctx = data.GetContext()

	if len(ctx.GetTraceId()) == 0 {
		newId := uuid.NewUUID().String()
		ctx.TraceId = &newId
	}
	timeNow := time.Now().Unix()
	ctx.Trail = append(ctx.Trail, &NatsContext_Trail{&(n.Opts.Name), &requestType, &timeNow, nil})

}

// Wrapper for stan Publish function. Needs a payload which has
// a context object (see PayloadWithContext type)
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) Publish(subject string, data PayloadWithContext) error {
	n.updateContext(data, RequestType_PUB)
	bData, err := proto.Marshal(data)
	if err != nil {
		return err
	}
	return n.Con.Publish(subject, bData)
}

// Wrapper for stan PublishAsync function with context.
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) PublishAsync(subject string, data PayloadWithContext, ackH stan.AckHandler) (string, error) {
	n.updateContext(data, RequestType_PUB)
	bData, err := proto.Marshal(data)
	if err != nil {
		return "", err
	}
	return n.Con.PublishAsync(subject, bData, ackH)
}

// Wrapper for stan PublishWithReply function with context.
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) PublishWithReply(subject string, reply string, data PayloadWithContext) error {
	n.updateContext(data, RequestType_PUBREQ)
	bData, err := proto.Marshal(data)
	if err != nil {
		return err
	}
	return n.Con.PublishWithReply(subject, reply, bData)
}

// Wrapper for stan PublishAsnycWithReply function with context.
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) PublishAsyncWithReply(subject string, reply string, data PayloadWithContext, ackH stan.AckHandler) (string, error) {
	n.updateContext(data, RequestType_PUBREQ)
	bData, err := proto.Marshal(data)
	if err != nil {
		return "", err
	}
	return n.Con.PublishAsyncWithReply(subject, reply, bData, ackH)
}

// Unsubscribe all handlers
func (n *Stan) UnsubscribeAll() {
	for _, route := range n.Opts.routes {
		route.subsc.Unsubscribe()
	}
}
<commit_msg>updated to remove old nats streaming functions<commit_after>package streaming

import (
	"errors"
	. "github.com/byrnedo/apibase/natsio/protobuf"
	"github.com/gogo/protobuf/proto"
	"github.com/nats-io/go-nats-streaming"
	"github.com/pborman/uuid"
	"time"
)

// stan.Options wrapper.
type Stan struct {
	Opts *StanOptions
	Con  stan.Conn
}

// Subscribe and record subscription to routes
func (n *Stan) Subscribe(route string, handler stan.MsgHandler, opts ...stan.SubscriptionOption) error {
	subsc, err := n.Con.Subscribe(route, handler, opts...)
	if err != nil {
		return errors.New("Failed to make subcriptions for " + route + ": " + err.Error())
	}
	n.Opts.routes = append(n.Opts.routes, &Route{route: route, handler: handler, subsc: subsc})
	return nil
}

// Subscribe to queue group and record subscription to routes
func (n *Stan) QueueSubscribe(route string, group string, handler stan.MsgHandler, opts ...stan.SubscriptionOption) error {
	subsc, err := n.Con.QueueSubscribe(route, group, handler, opts...)
	if err != nil {
		return errors.New("Failed to make subcriptions for " + route + ": " + err.Error())
	}
	n.Opts.routes = append(n.Opts.routes, &Route{route: route, handler: handler, subsc: subsc, queueGroup: group})
	return nil
}

// For use when using nats request/publish/publishrequest wrapper functions
type PayloadWithContext interface {
	proto.Message
	GetContext() *NatsContext
	SetContext(*NatsContext)
}

// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) updateContext(data PayloadWithContext, requestType RequestType) {
	var ctx *NatsContext

	if data.GetContext() == nil {
		data.SetContext(&NatsContext{})
	}

	ctx = data.GetContext()

	if len(ctx.GetTraceId()) == 0 {
		newId := uuid.NewUUID().String()
		ctx.TraceId = &newId
	}
	timeNow := time.Now().Unix()
	ctx.Trail = append(ctx.Trail, &NatsContext_Trail{&(n.Opts.Name), &requestType, &timeNow, nil})

}

// Wrapper for stan Publish function. Needs a payload which has
// a context object (see PayloadWithContext type)
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) Publish(subject string, data PayloadWithContext) error {
	n.updateContext(data, RequestType_PUB)
	bData, err := proto.Marshal(data)
	if err != nil {
		return err
	}
	return n.Con.Publish(subject, bData)
}

// Wrapper for stan PublishAsync function with context.
// Adds a context if it doesn't exist. Otherwise appends which app and time
// that this message is being sent at.
// Adds a traceID if not already there
func (n *Stan) PublishAsync(subject string, data PayloadWithContext, ackH stan.AckHandler) (string, error) {
	n.updateContext(data, RequestType_PUB)
	bData, err := proto.Marshal(data)
	if err != nil {
		return "", err
	}
	return n.Con.PublishAsync(subject, bData, ackH)
}

// Unsubscribe all handlers
func (n *Stan) UnsubscribeAll() {
	for _, route := range n.Opts.routes {
		route.subsc.Unsubscribe()
	}
}
<|endoftext|>


<commit_before>package filesystem

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"subuk/vmango/compute"
	"subuk/vmango/util"

	"golang.org/x/crypto/ssh"

	"github.com/rs/zerolog"
)

const KEY_UPLOAD_FILENAME = "vmango_authorized_keys"

type KeyRepository struct {
	filename string
	logger   zerolog.Logger
}

func NewKeyRepository(filename string, logger zerolog.Logger) (*KeyRepository, error) {
	dirname := filepath.Dir(filename)
	if err := os.MkdirAll(dirname, 0755); err != nil {
		return nil, util.NewError(err, "cannot create base directory")
	}
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, util.NewError(err, "cannot open key file for writing")
	}
	defer file.Close()

	repo := &KeyRepository{filename: filename, logger: logger}
	return repo, nil
}

func (repo *KeyRepository) parseKey(input []byte) (*compute.Key, error) {
	pubkey, comment, options, _, err := ssh.ParseAuthorizedKey(input)
	if err != nil {
		return nil, err
	}
	key := &compute.Key{
		Type:        pubkey.Type(),
		Value:       input,
		Comment:     comment,
		Options:     options,
		Fingerprint: ssh.FingerprintLegacyMD5(pubkey),
	}
	return key, nil
}

func (repo *KeyRepository) List() ([]*compute.Key, error) {
	file, err := os.Open(repo.filename)
	if err != nil {
		return nil, util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	keys := []*compute.Key{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		key, err := repo.parseKey(scanner.Bytes())
		if err != nil {
			repo.logger.Warn().Err(err).Msg("ignoring invalid key line")
			continue
		}
		keys = append(keys, key)
	}
	return keys, nil
}

func (repo *KeyRepository) Get(fingerprint string) (*compute.Key, error) {
	keys, err := repo.List()
	if err != nil {
		return nil, util.NewError(err, "cannot load keys")
	}
	for _, key := range keys {
		if key.Fingerprint == fingerprint {
			return key, nil
		}
	}
	return nil, compute.ErrKeyNotFound
}

func (repo *KeyRepository) Add(inputStr string) error {
	input := bytes.TrimSpace([]byte(inputStr))

	newKey, err := repo.parseKey(input)
	if err != nil {
		return util.NewError(err, "cannot parse provided key")
	}
	existingKey, err := repo.Get(newKey.Fingerprint)
	if err != nil && err != compute.ErrKeyNotFound {
		return util.NewError(err, "cannot check if key already exists")
	}
	if existingKey != nil {
		return compute.ErrKeyAlreadyExists
	}
	file, err := os.OpenFile(repo.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	input = append(input, '\n')
	if _, err := file.Write(input); err != nil {
		return util.NewError(err, "cannot write key")
	}

	return nil
}

func (repo *KeyRepository) Delete(fingerprint string) error {
	file, err := os.Open(repo.filename)
	if err != nil {
		return util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	keyFound := false
	contentWithoutKey := []byte{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := bytes.TrimSpace(scanner.Bytes())
		if bytes.Equal(line, []byte("")) {
			continue
		}
		key, err := repo.parseKey(line)
		line = append(line, '\n')
		if err != nil {
			contentWithoutKey = append(contentWithoutKey, line...)
			continue
		}
		if key.Fingerprint != fingerprint {
			contentWithoutKey = append(contentWithoutKey, line...)
			continue
		}
		keyFound = true
	}
	if !keyFound {
		return compute.ErrKeyNotFound
	}
	if err := ioutil.WriteFile(file.Name(), contentWithoutKey, 0644); err != nil {
		return util.NewError(err, "cannot rewrite key file")
	}
	return nil
}
<commit_msg>Allow to use readonly key file<commit_after>package filesystem

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"subuk/vmango/compute"
	"subuk/vmango/util"

	"golang.org/x/crypto/ssh"

	"github.com/rs/zerolog"
)

const KEY_UPLOAD_FILENAME = "vmango_authorized_keys"

type KeyRepository struct {
	filename string
	logger   zerolog.Logger
}

func NewKeyRepository(filename string, logger zerolog.Logger) (*KeyRepository, error) {
	dirname := filepath.Dir(filename)
	if err := os.MkdirAll(dirname, 0755); err != nil {
		return nil, util.NewError(err, "cannot create base directory")
	}
	repo := &KeyRepository{filename: filename, logger: logger}
	return repo, nil
}

func (repo *KeyRepository) parseKey(input []byte) (*compute.Key, error) {
	pubkey, comment, options, _, err := ssh.ParseAuthorizedKey(input)
	if err != nil {
		return nil, err
	}
	key := &compute.Key{
		Type:        pubkey.Type(),
		Value:       input,
		Comment:     comment,
		Options:     options,
		Fingerprint: ssh.FingerprintLegacyMD5(pubkey),
	}
	return key, nil
}

func (repo *KeyRepository) List() ([]*compute.Key, error) {
	file, err := os.Open(repo.filename)
	if err != nil {
		return nil, util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	keys := []*compute.Key{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		key, err := repo.parseKey(scanner.Bytes())
		if err != nil {
			repo.logger.Warn().Err(err).Msg("ignoring invalid key line")
			continue
		}
		keys = append(keys, key)
	}
	return keys, nil
}

func (repo *KeyRepository) Get(fingerprint string) (*compute.Key, error) {
	keys, err := repo.List()
	if err != nil {
		return nil, util.NewError(err, "cannot load keys")
	}
	for _, key := range keys {
		if key.Fingerprint == fingerprint {
			return key, nil
		}
	}
	return nil, compute.ErrKeyNotFound
}

func (repo *KeyRepository) Add(inputStr string) error {
	input := bytes.TrimSpace([]byte(inputStr))

	newKey, err := repo.parseKey(input)
	if err != nil {
		return util.NewError(err, "cannot parse provided key")
	}
	existingKey, err := repo.Get(newKey.Fingerprint)
	if err != nil && err != compute.ErrKeyNotFound {
		return util.NewError(err, "cannot check if key already exists")
	}
	if existingKey != nil {
		return compute.ErrKeyAlreadyExists
	}
	file, err := os.OpenFile(repo.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	input = append(input, '\n')
	if _, err := file.Write(input); err != nil {
		return util.NewError(err, "cannot write key")
	}

	return nil
}

func (repo *KeyRepository) Delete(fingerprint string) error {
	file, err := os.Open(repo.filename)
	if err != nil {
		return util.NewError(err, "cannot open key file")
	}
	defer file.Close()

	keyFound := false
	contentWithoutKey := []byte{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := bytes.TrimSpace(scanner.Bytes())
		if bytes.Equal(line, []byte("")) {
			continue
		}
		key, err := repo.parseKey(line)
		line = append(line, '\n')
		if err != nil {
			contentWithoutKey = append(contentWithoutKey, line...)
			continue
		}
		if key.Fingerprint != fingerprint {
			contentWithoutKey = append(contentWithoutKey, line...)
			continue
		}
		keyFound = true
	}
	if !keyFound {
		return compute.ErrKeyNotFound
	}
	if err := ioutil.WriteFile(file.Name(), contentWithoutKey, 0644); err != nil {
		return util.NewError(err, "cannot rewrite key file")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"os"

	"github.com/Shopify/sarama"
	"github.com/wvanbergen/kazoo-go"
)

var (
	zookeeper = flag.String("zookeeper", os.Getenv("ZOOKEEPER_PEERS"), "The zookeeper connection string")
	groupName = flag.String("group", "", "The consumer group to transfer offsets for")
)

func main() {
	flag.Parse()

	if *zookeeper == "" {
		log.Fatal("The -zookeeper command line argument is required")
	}

	if *groupName == "" {
		log.Fatal("The -group command line argument is required")
	}

	var (
		config         = kazoo.NewConfig()
		zookeeperNodes []string
	)

	zookeeperNodes, config.Chroot = kazoo.ParseConnectionString(*zookeeper)
	kz, err := kazoo.NewKazoo(zookeeperNodes, config)
	if err != nil {
		log.Fatal("[ERROR] Failed to connect to the zookeeper cluster:", err)
	}
	defer kz.Close()

	brokerList, err := kz.BrokerList()
	if err != nil {
		log.Fatal("[ERROR] Failed to retrieve Kafka broker list from zookeeper:", err)
	}

	group := kz.Consumergroup(*groupName)
	if exists, err := group.Exists(); err != nil {
		log.Fatal(err)
	} else if !exists {
		log.Fatalf("[ERROR] The consumergroup %s is not registered in Zookeeper", *groupName)
	}

	if instances, err := group.Instances(); err != nil {
		log.Fatal("[ERROR] Failed to get running instances from Zookeeper:", err)
	} else if len(instances) > 0 {
		log.Printf("[WARNING] This consumergroup has %d running instances. You should probably stop them before transferring offsets.", len(instances))
	}

	offsets, err := group.FetchAllOffsets()
	if err != nil {
		log.Fatal("[ERROR] Failed to retrieve offsets from zookeeper:", err)
	}

	client, err := sarama.NewClient(brokerList, nil)
	if err != nil {
		log.Fatal("[ERROR] Failed to connect to Kafka cluster:", err)
	}
	defer client.Close()

	coordinator, err := client.Coordinator(group.Name)
	if err != nil {
		log.Fatal("[ERROR] Failed to obtain coordinator for consumer group:", err)
	}

	request := &sarama.OffsetCommitRequest{
		Version:       1,
		ConsumerGroup: group.Name,
	}

	for topic, partitionOffsets := range offsets {
		for partition, nextOffset := range partitionOffsets {
			// In Zookeeper, we store the next offset to process.
			// In Kafka, we store the last offset that was processed.
			// So we have to fix an off by one error.
			lastOffset := nextOffset - 1
			request.AddBlock(topic, partition, lastOffset, 0, "")
		}
	}

	response, err := coordinator.CommitOffset(request)
	if err != nil {
		log.Fatal("[ERROR] Failed to commit offsets to Kafka:", err)
	}

	var errorsFound bool
	for topic, partitionOffsets := range offsets {
		for partition, nextOffset := range partitionOffsets {
			if err := response.Errors[topic][partition]; err == sarama.ErrNoError {
				log.Printf("%s/%d: %d committed as last processed offset", topic, partition, nextOffset-1)
			} else {
				errorsFound = true
				log.Printf("[WARNING] %s/%d: offset %d was not committed: %s", topic, partition, nextOffset-1, err)
			}
		}
	}

	if !errorsFound {
		log.Print("[SUCCESS] Offsets successfully committed to Kafka!")
	}
}
<commit_msg>Transfer offsets tool: guard against faulty responses<commit_after>package main

import (
	"flag"
	"log"
	"os"

	"github.com/Shopify/sarama"
	"github.com/wvanbergen/kazoo-go"
)

var (
	zookeeper = flag.String("zookeeper", os.Getenv("ZOOKEEPER_PEERS"), "The zookeeper connection string")
	groupName = flag.String("group", "", "The consumer group to transfer offsets for")
)

func main() {
	flag.Parse()

	if *zookeeper == "" {
		log.Fatal("The -zookeeper command line argument is required")
	}

	if *groupName == "" {
		log.Fatal("The -group command line argument is required")
	}

	var (
		config         = kazoo.NewConfig()
		zookeeperNodes []string
	)

	zookeeperNodes, config.Chroot = kazoo.ParseConnectionString(*zookeeper)
	kz, err := kazoo.NewKazoo(zookeeperNodes, config)
	if err != nil {
		log.Fatal("[ERROR] Failed to connect to the zookeeper cluster:", err)
	}
	defer kz.Close()

	brokerList, err := kz.BrokerList()
	if err != nil {
		log.Fatal("[ERROR] Failed to retrieve Kafka broker list from zookeeper:", err)
	}

	group := kz.Consumergroup(*groupName)
	if exists, err := group.Exists(); err != nil {
		log.Fatal(err)
	} else if !exists {
		log.Fatalf("[ERROR] The consumergroup %s is not registered in Zookeeper", *groupName)
	}

	if instances, err := group.Instances(); err != nil {
		log.Fatal("[ERROR] Failed to get running instances from Zookeeper:", err)
	} else if len(instances) > 0 {
		log.Printf("[WARNING] This consumergroup has %d running instances. You should probably stop them before transferring offsets.", len(instances))
	}

	offsets, err := group.FetchAllOffsets()
	if err != nil {
		log.Fatal("[ERROR] Failed to retrieve offsets from zookeeper:", err)
	}

	client, err := sarama.NewClient(brokerList, nil)
	if err != nil {
		log.Fatal("[ERROR] Failed to connect to Kafka cluster:", err)
	}
	defer client.Close()

	coordinator, err := client.Coordinator(group.Name)
	if err != nil {
		log.Fatal("[ERROR] Failed to obtain coordinator for consumer group:", err)
	}

	request := &sarama.OffsetCommitRequest{
		Version:       1,
		ConsumerGroup: group.Name,
	}

	for topic, partitionOffsets := range offsets {
		for partition, nextOffset := range partitionOffsets {
			// In Zookeeper, we store the next offset to process.
			// In Kafka, we store the last offset that was processed.
			// So we have to fix an off by one error.
			lastOffset := nextOffset - 1
			request.AddBlock(topic, partition, lastOffset, 0, "")
		}
	}

	response, err := coordinator.CommitOffset(request)
	if err != nil {
		log.Fatal("[ERROR] Failed to commit offsets to Kafka:", err)
	}

	var errorsFound bool
	for topic, partitionOffsets := range offsets {
		for partition, nextOffset := range partitionOffsets {
			if _, ok := response.Errors[topic]; !ok {
				log.Printf("[WARNING] %s/%d: topic was not present in response and may not be committed!", topic, partition)
				continue
			}
			if _, ok := response.Errors[topic][partition]; !ok {
				log.Printf("[WARNING] %s/%d: partition was not present in response and may not be committed!", topic, partition)
				continue
			}

			if err := response.Errors[topic][partition]; err == sarama.ErrNoError {
				log.Printf("%s/%d: %d committed as last processed offset", topic, partition, nextOffset-1)
			} else {
				errorsFound = true
				log.Printf("[WARNING] %s/%d: offset %d was not committed: %s", topic, partition, nextOffset-1, err)
			}
		}
	}

	if !errorsFound {
		log.Print("[SUCCESS] Offsets successfully committed to Kafka!")
	}
}
<|endoftext|>


<commit_before>package grandRepositorySky

//IMPORT parts ----------------------------------------------------------
import (
	"bytes"
	"fmt"
	"github.com/tgermain/grandRepositorySky/dht"
	// "math/big"
)

//Const parts -----------------------------------------------------------
const SPACESIZE = 160

//Objects parts ---------------------------------------------------------
type DHTnode struct {
	id        string
	fingers   []*fingerEntry //Successor is fingers[0].tmp
	successor *fingerEntry
	ip, port  string
}

type fingerEntry struct {
	idKey  string
	idResp string
	tmp    *DHTnode
}

//Method parts ----------------------------------------------------------

func (currentNode *DHTnode) AddToRing(newNode *DHTnode) {
	//furthers comments assume that he current currentNode is named x
	switch {
	case bytes.Compare([]byte(currentNode.id), []byte(currentNode.successor.tmp.id)) == 0:
		{
			//init case : currentNode looping on itself
			// fmt.Println("Init case : 2 node")

			currentNode.chainingToTheRing(newNode)
			//TODO : initialize both fingers tables
		}
	case dht.Between(currentNode.id, currentNode.successor.tmp.id, newNode.id):
		// (currentNode.id < newNode.id) && (newNode.id < currentNode.successor.tmp.id)
		{
			//case of x->(x+2) and we want to add (x+1) node
			// fmt.Println("add in the middle")
			currentNode.chainingToTheRing(newNode)
		}
	case dht.Between(currentNode.successor.tmp.id, newNode.id, currentNode.id):
		// (currentNode.successor.tmp.id < currentNode.id) && (currentNode.id < newNode.id)
		{
			//case of X -> 0 and we want to add (x+1) node
			// fmt.Println("add at the end")
			currentNode.chainingToTheRing(newNode)
		}
	default:
		{
			// fmt.Println("Go to the next")
			currentNode.successor.tmp.AddToRing(newNode)
		}
	}
}

func (currentNode *DHTnode) chainingToTheRing(newNode *DHTnode) {

	// fmt.Println("old node : ")
	// currentNode.PrintNodeInfo()
	// fmt.Println("new node : ")
	// newNode.PrintNodeInfo()

	newNode.successor.tmp = currentNode.successor.tmp

	currentNode.successor.tmp = newNode

	// fmt.Println("============================================")
	// fmt.Println("old node : ")
	// currentNode.PrintNodeInfo()
	// fmt.Println("new node : ")
	// newNode.PrintNodeInfo()

	newNode.initFingersTable()
	// currentNode.initFingersTable()
}

func (currentNode *DHTnode) Lookup(idToSearch string) *DHTnode {
	if dht.Between(currentNode.id, currentNode.fingers[0].tmp.id, idToSearch) {
		// fmt.Printf("We are seeking for %s\n", idToSearch)
		return currentNode
	} else {
		// fmt.Println("go to the next one")
		//TODO use the fingers table here
		return currentNode.findClosestNode(idToSearch).Lookup(idToSearch)
	}
}

func (currentNode *DHTnode) findClosestNode(idToSearch string) *DHTnode {
	bestFinger := currentNode.successor.tmp

	minDistance := dht.Distance([]byte(currentNode.successor.tmp.id), []byte(idToSearch), SPACESIZE)
	for _, v := range currentNode.fingers {
		if v != nil {
			//if a member of finger table brought closer than the actual one, we udate the value of minDistance and of the chosen finger
			currentDistance := dht.Distance([]byte(v.idResp), []byte(idToSearch), SPACESIZE)

			// x.cmp(y)
			// -1 if x <  y
			//  0 if x == y
			// +1 if x >  y

			if minDistance.Cmp(currentDistance) == 1 {
				minDistance = currentDistance
				bestFinger = v.tmp
			}
		}
	}
	fmt.Printf("From [%s] We have found the bes way to go to [%s] : we go throught node [%s]\n", currentNode.id, idToSearch, bestFinger.id)
	return bestFinger
}

func (node *DHTnode) initFingersTable() {
	fmt.Printf("****************************************************************Node [%s] : init finger table \n", node.id)
	for i := 0; i < SPACESIZE; i++ {
		fmt.Printf("Calculatin fingers [%d]\n", i)
		fingerId, _ := dht.CalcFinger([]byte(node.id), i+1, SPACESIZE)
		responsibleNode := node.Lookup(fingerId)
		node.fingers[i] = &fingerEntry{fingerId, responsibleNode.id, responsibleNode}

	}
	fmt.Println("****************************************************************Fingers table init DONE : ")
}

func (node *DHTnode) PrintRing() {
	fmt.Printf("%s\n", node.id)
	node.successor.tmp.printRingRec(node.id)
}

func (node *DHTnode) printRingRec(origId string) {
	fmt.Printf("%s\n", node.id)
	if bytes.Compare([]byte(node.successor.tmp.id), []byte(origId)) != 0 {

		node.successor.tmp.printRingRec(origId)
	}
}

func (node *DHTnode) TestCalcFingers(k, m int) {
	fingersId, _ := dht.CalcFinger([]byte(node.id), k, m)
	node.Lookup(fingersId).PrintNodeInfo()
}

func (node *DHTnode) PrintNodeInfo() {
	fmt.Println("---------------------------------")
	fmt.Println("Node info")
	fmt.Println("---------------------------------")
	fmt.Printf("	Id			%s\n", node.id)
	fmt.Printf("	Ip			%s\n", node.ip)
	fmt.Printf("	Port		%s\n", node.port)
	fmt.Printf(" 	Succesor	%s\n", node.successor.tmp.id)
	fmt.Println()
	// fmt.Println("  Fingers table :")
	// fmt.Println("  ---------------------------------")
	// fmt.Println("  Index		idkey			idNode ")
	// for i, v := range node.fingers {
	// 	if v != nil {
	// 		fmt.Printf("  %d 		%s					%s\n", i, v.idKey, v.idResp)
	// 	}
	// }
	fmt.Println("---------------------------------")

}

//other functions parts --------------------------------------------------------
func MakeDHTNode(NewId *string, NewIp, NewPort string) *DHTnode {
	if NewId == nil {
		tempId := dht.GenerateNodeId()
		NewId = &tempId
	}
	daNode := DHTnode{
		id:      *NewId,
		fingers: make([]*fingerEntry, SPACESIZE),
		ip:      NewIp,
		port:    NewPort,
	}
	daNode.successor = &fingerEntry{"truc", "bidule", nil}
	daNode.successor.tmp = &daNode
	daNode.successor.idResp = daNode.id
	// initialization of fingers table is done while adding the node to the ring
	// The fingers table of the first node of a ring is initialized when a second node is added to the ring

	//Initialize the finger table with each finger pointing to the node frehly created itself
	return &daNode
}
<commit_msg>lookup use use findClosestNode (finger table optimization)<commit_after>package grandRepositorySky

//IMPORT parts ----------------------------------------------------------
import (
	"bytes"
	"fmt"
	"github.com/tgermain/grandRepositorySky/dht"
	// "math/big"
)

//Const parts -----------------------------------------------------------
const SPACESIZE = 160

//Objects parts ---------------------------------------------------------
type DHTnode struct {
	id        string
	fingers   []*fingerEntry //Successor is fingers[0].tmp
	successor *fingerEntry
	ip, port  string
}

type fingerEntry struct {
	idKey  string
	idResp string
	tmp    *DHTnode
}

//Method parts ----------------------------------------------------------

func (currentNode *DHTnode) AddToRing(newNode *DHTnode) {
	//furthers comments assume that he current currentNode is named x
	switch {
	case bytes.Compare([]byte(currentNode.id), []byte(currentNode.successor.tmp.id)) == 0:
		{
			//init case : currentNode looping on itself
			// fmt.Println("Init case : 2 node")

			currentNode.chainingToTheRing(newNode)
			//TODO : initialize both fingers tables
		}
	case dht.Between(currentNode.id, currentNode.successor.tmp.id, newNode.id):
		// (currentNode.id < newNode.id) && (newNode.id < currentNode.successor.tmp.id)
		{
			//case of x->(x+2) and we want to add (x+1) node
			// fmt.Println("add in the middle")
			currentNode.chainingToTheRing(newNode)
		}
	case dht.Between(currentNode.successor.tmp.id, newNode.id, currentNode.id):
		// (currentNode.successor.tmp.id < currentNode.id) && (currentNode.id < newNode.id)
		{
			//case of X -> 0 and we want to add (x+1) node
			// fmt.Println("add at the end")
			currentNode.chainingToTheRing(newNode)
		}
	default:
		{
			// fmt.Println("Go to the next")
			currentNode.successor.tmp.AddToRing(newNode)
		}
	}
}

func (currentNode *DHTnode) chainingToTheRing(newNode *DHTnode) {

	// fmt.Println("old node : ")
	// currentNode.PrintNodeInfo()
	// fmt.Println("new node : ")
	// newNode.PrintNodeInfo()

	newNode.successor.tmp = currentNode.successor.tmp

	currentNode.successor.tmp = newNode

	// fmt.Println("============================================")
	// fmt.Println("old node : ")
	// currentNode.PrintNodeInfo()
	// fmt.Println("new node : ")
	// newNode.PrintNodeInfo()

	newNode.initFingersTable()
	// currentNode.initFingersTable()
}

func (currentNode *DHTnode) Lookup(idToSearch string) *DHTnode {
	fmt.Printf("Node [%s] made a lookup to [%s]\n", currentNode.id, idToSearch)
	currentNode.PrintNodeInfo()
	switch {
	case currentNode.id == currentNode.successor.tmp.id:
		{
			return currentNode
		}
	case dht.Between(currentNode.id, currentNode.successor.tmp.id, idToSearch):
		{
			fmt.Printf("We were seeking for %s, our journey is now finished\n", idToSearch)
			return currentNode
		}
	default:
		{
			// fmt.Println("go to the next one")
			//TODO use the fingers table here
			return currentNode.findClosestNode(idToSearch).Lookup(idToSearch)
		}
	}
}

func (currentNode *DHTnode) findClosestNode(idToSearch string) *DHTnode {
	bestFinger := currentNode.successor.tmp

	minDistance := dht.Distance([]byte(currentNode.successor.tmp.id), []byte(idToSearch), SPACESIZE)
	for _, v := range currentNode.fingers {
		if v != nil {
			//if a member of finger table brought closer than the actual one, we udate the value of minDistance and of the chosen finger
			currentDistance := dht.Distance([]byte(v.idResp), []byte(idToSearch), SPACESIZE)

			// x.cmp(y)
			// -1 if x <  y
			//  0 if x == y
			// +1 if x >  y

			if minDistance.Cmp(currentDistance) == 1 {
				minDistance = currentDistance
				bestFinger = v.tmp
			}
		}
	}
	fmt.Printf("From [%s] We have found the bes way to go to [%s] : we go throught node [%s]\n", currentNode.id, idToSearch, bestFinger.id)
	return bestFinger
}

func (node *DHTnode) initFingersTable() {
	fmt.Printf("****************************************************************Node [%s] : init finger table \n", node.id)
	for i := 0; i < SPACESIZE; i++ {
		fmt.Printf("Calculatin fingers [%d]\n", i)
		fingerId, _ := dht.CalcFinger([]byte(node.id), i+1, SPACESIZE)
		responsibleNode := node.Lookup(fingerId)
		node.fingers[i] = &fingerEntry{fingerId, responsibleNode.id, responsibleNode}

	}
	fmt.Println("****************************************************************Fingers table init DONE : ")
}

func (node *DHTnode) PrintRing() {
	fmt.Printf("%s\n", node.id)
	node.successor.tmp.printRingRec(node.id)
}

func (node *DHTnode) printRingRec(origId string) {
	fmt.Printf("%s\n", node.id)
	if bytes.Compare([]byte(node.successor.tmp.id), []byte(origId)) != 0 {

		node.successor.tmp.printRingRec(origId)
	}
}

func (node *DHTnode) TestCalcFingers(k, m int) {
	fingersId, _ := dht.CalcFinger([]byte(node.id), k, m)
	node.Lookup(fingersId).PrintNodeInfo()
}

func (node *DHTnode) PrintNodeInfo() {
	fmt.Println("---------------------------------")
	fmt.Println("Node info")
	fmt.Println("---------------------------------")
	fmt.Printf("	Id			%s\n", node.id)
	fmt.Printf("	Ip			%s\n", node.ip)
	fmt.Printf("	Port		%s\n", node.port)
	fmt.Printf(" 	Succesor	%s\n", node.successor.tmp.id)
	fmt.Println()
	// fmt.Println("  Fingers table :")
	// fmt.Println("  ---------------------------------")
	// fmt.Println("  Index		idkey			idNode ")
	// for i, v := range node.fingers {
	// 	if v != nil {
	// 		fmt.Printf("  %d 		%s					%s\n", i, v.idKey, v.idResp)
	// 	}
	// }
	fmt.Println("---------------------------------")

}

//other functions parts --------------------------------------------------------
func MakeDHTNode(NewId *string, NewIp, NewPort string) *DHTnode {
	if NewId == nil {
		tempId := dht.GenerateNodeId()
		NewId = &tempId
	}
	daNode := DHTnode{
		id:      *NewId,
		fingers: make([]*fingerEntry, SPACESIZE),
		ip:      NewIp,
		port:    NewPort,
	}
	daNode.successor = &fingerEntry{"truc", "bidule", nil}
	daNode.successor.tmp = &daNode
	daNode.successor.idResp = daNode.id
	// initialization of fingers table is done while adding the node to the ring
	// The fingers table of the first node of a ring is initialized when a second node is added to the ring

	//Initialize the finger table with each finger pointing to the node frehly created itself
	return &daNode
}
<|endoftext|>