Scheduler Hints

This commit adds the ability to specify scheduling hints for the
instances. Scheduling hints alter the placement of instances in
the OpenStack cloud by using a variety of attributes.
diff --git a/acceptance/openstack/compute/v2/servergroup_test.go b/acceptance/openstack/compute/v2/servergroup_test.go
index 80015e1..945854e 100644
--- a/acceptance/openstack/compute/v2/servergroup_test.go
+++ b/acceptance/openstack/compute/v2/servergroup_test.go
@@ -3,10 +3,15 @@
 package v2
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/acceptance/tools"
+	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints"
 	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups"
+	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 func createServerGroup(t *testing.T, computeClient *gophercloud.ServiceClient) (*servergroups.ServerGroup, error) {
@@ -36,7 +41,53 @@
 	return nil
 }
 
+func createServerInGroup(t *testing.T, computeClient *gophercloud.ServiceClient, choices *ComputeChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
+	if testing.Short() {
+		t.Skip("Skipping test that requires server creation in short mode.")
+	}
+
+	name := tools.RandomString("ACPTTEST", 16)
+	t.Logf("Attempting to create server: %s\n", name)
+
+	pwd := tools.MakeNewPassword("")
+
+	serverCreateOpts := servers.CreateOpts{
+		Name:      name,
+		FlavorRef: choices.FlavorID,
+		ImageRef:  choices.ImageID,
+		AdminPass: pwd,
+	}
+	server, err := servers.Create(computeClient, schedulerhints.CreateOptsExt{
+		serverCreateOpts,
+		schedulerhints.SchedulerHints{
+			Group: serverGroup.ID,
+		},
+	}).Extract()
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+
+	th.AssertEquals(t, pwd, server.AdminPass)
+
+	return server, err
+}
+
+func verifySchedulerWorked(t *testing.T, firstServer, secondServer *servers.Server) error {
+	t.Logf("First server hostID: %v", firstServer.HostID)
+	t.Logf("Second server hostID: %v", secondServer.HostID)
+	if firstServer.HostID == secondServer.HostID {
+		return nil
+	}
+
+	return fmt.Errorf("%s and %s were not scheduled on the same host.", firstServer.ID, secondServer.ID)
+}
+
 func TestServerGroups(t *testing.T) {
+	choices, err := ComputeChoicesFromEnv()
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	computeClient, err := newClient()
 	if err != nil {
 		t.Fatalf("Unable to create a compute client: %v", err)
@@ -48,11 +99,45 @@
 	}
 	defer func() {
 		servergroups.Delete(computeClient, sg.ID)
-		t.Logf("ServerGroup deleted.")
+		t.Logf("Server Group deleted.")
 	}()
 
 	err = getServerGroup(t, computeClient, sg.ID)
 	if err != nil {
 		t.Fatalf("Unable to get server group: %v", err)
 	}
+
+	firstServer, err := createServerInGroup(t, computeClient, choices, sg)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer func() {
+		servers.Delete(computeClient, firstServer.ID)
+		t.Logf("Server deleted.")
+	}()
+
+	if err = waitForStatus(computeClient, firstServer, "ACTIVE"); err != nil {
+		t.Fatalf("Unable to wait for server: %v", err)
+	}
+
+	firstServer, err = servers.Get(computeClient, firstServer.ID).Extract()
+
+	secondServer, err := createServerInGroup(t, computeClient, choices, sg)
+	if err != nil {
+		t.Fatalf("Unable to create server: %v", err)
+	}
+	defer func() {
+		servers.Delete(computeClient, secondServer.ID)
+		t.Logf("Server deleted.")
+	}()
+
+	if err = waitForStatus(computeClient, secondServer, "ACTIVE"); err != nil {
+		t.Fatalf("Unable to wait for server: %v", err)
+	}
+
+	secondServer, err = servers.Get(computeClient, secondServer.ID).Extract()
+
+	if err = verifySchedulerWorked(t, firstServer, secondServer); err != nil {
+		t.Fatalf("Scheduling did not work: %v", err)
+	}
 }