func TestStackTags(t *testing.T) {
	testCases := []struct {
		expectedTags []*cloudformation.Tag
		clusterYaml  string
	}{
		{
			expectedTags: []*cloudformation.Tag{},
			clusterYaml: `
#no stackTags set
`,
		},
		{
			expectedTags: []*cloudformation.Tag{
				&cloudformation.Tag{
					Key:   aws.String("KeyA"),
					Value: aws.String("ValueA"),
				},
				&cloudformation.Tag{
					Key:   aws.String("KeyB"),
					Value: aws.String("ValueB"),
				},
				&cloudformation.Tag{
					Key:   aws.String("KeyC"),
					Value: aws.String("ValueC"),
				},
			},
			clusterYaml: `
stackTags:
  KeyA: ValueA
  KeyB: ValueB
  KeyC: ValueC
`,
		},
	}

	for _, testCase := range testCases {
		configBody := defaultConfigValues(t, testCase.clusterYaml)
		clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
		if err != nil {
			t.Errorf("could not get valid cluster config: %v", err)
			continue
		}

		cluster := &Cluster{
			Cluster: *clusterConfig,
		}

		cfSvc := &dummyCloudformationService{
			ExpectedTags: testCase.expectedTags,
		}

		_, err = cluster.createStack(cfSvc, "")

		if err != nil {
			t.Errorf("error creating cluster: %v\nfor test case %+v", err, testCase)
		}
	}
}
func TestValidateDNSConfig(t *testing.T) {
	dnsConfig := `
createRecordSet: true
recordSetTTL: 60
hostedZone: staging.core-os.net
`

	configBody := minimalConfigYaml + dnsConfig
	clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
	if err != nil {
		t.Errorf("could not get valid cluster config: %v", err)
	}
	c := &Cluster{Cluster: *clusterConfig}

	r53 := dummyR53Service{
		HostedZones: []Zone{
			Zone{
				Id:  "staging_id",
				DNS: "staging.core-os.net.",
			},
		},
		ResourceRecordSets: map[string]string{
			"staging_id": "existing-record.staging.core-os.net.",
		},
	}

	if err := c.validateDNSConfig(r53); err != nil {
		t.Errorf("returned error for valid config: %v", err)
	}

	c.HostedZone = "non-existant-zone"
	if err := c.validateDNSConfig(r53); err == nil {
		t.Errorf("failed to catch non-existent hosted zone")
	}

	c.HostedZone = "staging.core-os.net"
	c.ExternalDNSName = "existing-record.staging.core-os.net"
	if err := c.validateDNSConfig(r53); err == nil {
		t.Errorf("failed to catch already existing ExternalDNSName")
	}
}
func TestValidateKeyPair(t *testing.T) {

	clusterConfig, err := config.ClusterFromBytes([]byte(defaultConfigValues(t, "")))
	if err != nil {
		t.Errorf("could not get valid cluster config: %v", err)
	}

	c := &Cluster{Cluster: *clusterConfig}

	ec2Svc := dummyEC2Service{}
	ec2Svc.KeyPairs = map[string]bool{
		c.KeyName: true,
	}

	if err := c.validateKeyPair(ec2Svc); err != nil {
		t.Errorf("returned an error for valid key")
	}

	c.KeyName = "invalidKeyName"
	if err := c.validateKeyPair(ec2Svc); err == nil {
		t.Errorf("failed to catch invalid key \"%s\"", c.KeyName)
	}
}
func TestValidateWorkerRootVolume(t *testing.T) {
	testCases := []struct {
		expectedRootVolume *ec2.CreateVolumeInput
		clusterYaml        string
	}{
		{
			expectedRootVolume: &ec2.CreateVolumeInput{
				Iops:       aws.Int64(0),
				Size:       aws.Int64(30),
				VolumeType: aws.String("gp2"),
			},
			clusterYaml: `
# no root volumes set
`,
		},
		{
			expectedRootVolume: &ec2.CreateVolumeInput{
				Iops:       aws.Int64(0),
				Size:       aws.Int64(30),
				VolumeType: aws.String("standard"),
			},
			clusterYaml: `
workerRootVolumeType: standard
`,
		},
		{
			expectedRootVolume: &ec2.CreateVolumeInput{
				Iops:       aws.Int64(0),
				Size:       aws.Int64(50),
				VolumeType: aws.String("gp2"),
			},
			clusterYaml: `
workerRootVolumeType: gp2
workerRootVolumeSize: 50
`,
		},
		{
			expectedRootVolume: &ec2.CreateVolumeInput{
				Iops:       aws.Int64(2000),
				Size:       aws.Int64(100),
				VolumeType: aws.String("io1"),
			},
			clusterYaml: `
workerRootVolumeType: io1
workerRootVolumeSize: 100
workerRootVolumeIOPS: 2000
`,
		},
	}

	for _, testCase := range testCases {
		configBody := defaultConfigValues(t, testCase.clusterYaml)
		clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
		if err != nil {
			t.Errorf("could not get valid cluster config: %v", err)
			continue
		}

		c := &Cluster{
			Cluster: *clusterConfig,
		}

		ec2Svc := &dummyEC2Service{
			ExpectedRootVolume: testCase.expectedRootVolume,
		}

		if err := c.validateWorkerRootVolume(ec2Svc); err != nil {
			t.Errorf("error creating cluster: %v\nfor test case %+v", err, testCase)
		}
	}
}
func TestValidateDNSConfig(t *testing.T) {
	r53 := dummyR53Service{
		HostedZones: []Zone{
			{
				Id:  "/hostedzone/staging_id_1",
				DNS: "staging.core-os.net.",
			},
			{
				Id:  "/hostedzone/staging_id_2",
				DNS: "staging.core-os.net.",
			},
			{
				Id:  "/hostedzone/staging_id_3",
				DNS: "zebras.coreos.com.",
			},
			{
				Id:  "/hostedzone/staging_id_4",
				DNS: "core-os.net.",
			},
		},
		ResourceRecordSets: map[string]string{
			"staging_id_1": "existing-record.staging.core-os.net.",
		},
	}

	validDNSConfigs := []string{
		`
createRecordSet: true
recordSetTTL: 60
hostedZone: core-os.net
`, `
createRecordSet: true
recordSetTTL: 60
hostedZoneId: staging_id_1
`, `
createRecordSet: true
recordSetTTL: 60
hostedZoneId: /hostedzone/staging_id_2
`,
	}

	invalidDNSConfigs := []string{
		`
createRecordSet: true
recordSetTTL: 60
hostedZone: staging.core-os.net # hostedZone is ambiguous
`, `
createRecordSet: true
recordSetTTL: 60
hostedZoneId: /hostedzone/staging_id_3 # <staging_id_id> is not a super-domain
`, `
createRecordSet: true
recordSetTTL: 60
hostedZone: zebras.coreos.com # zebras.coreos.com is not a super-domain
`, `
createRecordSet: true
recordSetTTL: 60
hostedZoneId: /hostedzone/staging_id_5 #non-existant hostedZoneId
`, `
createRecordSet: true
recordSetTTL: 60
hostedZone: unicorns.core-os.net  #non-existant hostedZone DNS name
`,
	}

	for _, validConfig := range validDNSConfigs {
		configBody := defaultConfigValues(t, validConfig)
		clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
		if err != nil {
			t.Errorf("could not get valid cluster config: %v", err)
			continue
		}
		c := &Cluster{Cluster: *clusterConfig}

		if err := c.validateDNSConfig(r53); err != nil {
			t.Errorf("returned error for valid config: %v", err)
		}
	}

	for _, invalidConfig := range invalidDNSConfigs {
		configBody := defaultConfigValues(t, invalidConfig)
		clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
		if err != nil {
			t.Errorf("could not get valid cluster config: %v", err)
			continue
		}
		c := &Cluster{Cluster: *clusterConfig}

		if err := c.validateDNSConfig(r53); err == nil {
			t.Errorf("failed to produce error for invalid config: %s", configBody)
		}
	}

}
func TestExistingVPCValidation(t *testing.T) {

	goodExistingVPCConfigs := []string{
		``, //Tests default create VPC mode, which bypasses existing VPC validation
		`
vpcCIDR: 10.5.0.0/16
vpcId: vpc-xxx1
routeTableId: rtb-xxxxxx
instanceCIDR: 10.5.11.0/24
controllerIP: 10.5.11.10
`, `
vpcCIDR: 192.168.1.0/24
vpcId: vpc-xxx2
instanceCIDR: 192.168.1.50/28
controllerIP: 192.168.1.50
`, `
vpcCIDR: 192.168.1.0/24
vpcId: vpc-xxx2
controllerIP: 192.168.1.5
subnets:
  - instanceCIDR: 192.168.1.0/28
  - instanceCIDR: 192.168.1.32/28
  - instanceCIDR: 192.168.1.64/28
`,
	}

	badExistingVPCConfigs := []string{
		`
vpcCIDR: 10.0.0.0/16
vpcId: vpc-xxx3 #vpc does not exist
instanceCIDR: 10.0.0.0/24
controllerIP: 10.0.0.50
routeTableId: rtb-xxxxxx
`, `
vpcCIDR: 10.10.0.0/16 #vpc cidr does match existing vpc-xxx1
vpcId: vpc-xxx1
instanceCIDR: 10.10.0.0/24
controllerIP: 10.10.0.50
routeTableId: rtb-xxxxxx
`, `
vpcCIDR: 10.5.0.0/16
instanceCIDR: 10.5.2.0/28 #instance cidr conflicts with existing subnet
controllerIP: 10.5.2.10
vpcId: vpc-xxx1
routeTableId: rtb-xxxxxx
`, `
vpcCIDR: 192.168.1.0/24
instanceCIDR: 192.168.1.100/26 #instance cidr conflicts with existing subnet
controllerIP: 192.168.1.80
vpcId: vpc-xxx2
routeTableId: rtb-xxxxxx
`, `
vpcCIDR: 192.168.1.0/24
controllerIP: 192.168.1.80
vpcId: vpc-xxx2
routeTableId: rtb-xxxxxx
subnets:
  - instanceCIDR: 192.168.1.100/26  #instance cidr conflicts with existing subnet
  - instanceCIDR: 192.168.1.0/26
`,
	}

	ec2Service := dummyEC2Service{
		VPCs: map[string]VPC{
			"vpc-xxx1": {
				cidr: "10.5.0.0/16",
				subnetCidrs: []string{
					"10.5.1.0/24",
					"10.5.2.0/24",
					"10.5.10.100/29",
				},
			},
			"vpc-xxx2": {
				cidr: "192.168.1.0/24",
				subnetCidrs: []string{
					"192.168.1.100/28",
					"192.168.1.150/28",
					"192.168.1.200/28",
				},
			},
		},
	}

	validateCluster := func(networkConfig string) error {
		configBody := defaultConfigValues(t, networkConfig)
		clusterConfig, err := config.ClusterFromBytes([]byte(configBody))
		if err != nil {
			t.Errorf("could not get valid cluster config: %v", err)
			return nil
		}

		cluster := &Cluster{
			Cluster: *clusterConfig,
		}

		return cluster.validateExistingVPCState(ec2Service)
	}

	for _, networkConfig := range goodExistingVPCConfigs {
		if err := validateCluster(networkConfig); err != nil {
			t.Errorf("Correct config tested invalid: %s\n%s", err, networkConfig)
		}
	}

	for _, networkConfig := range badExistingVPCConfigs {
		if err := validateCluster(networkConfig); err == nil {
			t.Errorf("Incorrect config tested valid, expected error:\n%s", networkConfig)
		}
	}
}