Epic refactoring to improve testability.

It all started when I wanted to write ListServers(), but doing that
meant acquiring the test context, so that I could intercept server-side
communications.  However, that couldn't be easily acquired with the old
software configuration.  In fact, it was patently impossible to do,
without breaking a lot of encapsulation and conflating concerns.

Additionally, I knew I didn't want to make ComputeApi() a method of an
AccessProvider interface; considering how similar the OpenStack APIs
are, I was banking on that design decision causing a lot of duplicate
code, even if said code was simple.  Not only that, but it conflated
concerns; again, something I wanted to avoid.

So, I needed to do a couple of things.

1) Realize that module-global functions are delegators to the global
context.  My original implementation of ComputeApi() wasn't, which meant
that I had zero access to any contexts created in unit testing.

2) Realize that the Context interface is the true Gophercloud global
API.  This meant I had to make a ComputeApi() method on the Context
interface, and implement it.  This proved very convenient -- it granted
me access automatically to all test contexts.

As a checklist bullet point, whenever adding a new global-level function
to gophercloud, do it in at least these steps: a) add the function as a
method on Context.  Seriously -- this is the only real way to make it
testable.  b) Add a very dumb delegator function in global_context.go
which dispatches to its eponymously-named method on globalContext.

3) Making this simple change was sufficient to start to test an
implementation of ListServers().  However, invoking "c := TestContext();
c.foo(); c.bar();" was becoming repetitive and error-prone.  So, I
refactored that into a Java-style DSL.  These things aren't terribly Go
idiomatic, but for my needs here, they work nicely.

4) I refactored the two different implementations of custom transports
into a single "transport-double" type.  This type will supports both
canned responses and some basic request validation.  It's expandable by
simply adding more configuration fields and DSL methods of its own.

5) api.go is no more -- it previously served two separate purposes, each
of which has its own source file now.  interfaces.go holds the
definition of all publicly visible APIs, while global_context.go
provides the default global Context, its initialization, and the
module-global entry points that delegate to the global context.

With these changes having been made, *now* I'm ready to actually start
testing ListServers() development!  It only took 24 hours and 4
refreshes of the feature branch just to get this far.  :-)

The nice thing is, though, that going forward, these changes should
contribute to making future endpoint binding implementations
significantly easier than what I had to do before.
diff --git a/service_catalog.go b/service_catalog.go
new file mode 100644
index 0000000..326f653
--- /dev/null
+++ b/service_catalog.go
@@ -0,0 +1,61 @@
+package gophercloud
+
+import (
+	"strings"
+)
+
+// ApiCriteria provides one or more criteria for the SDK to look for appropriate endpoints.
+// Fields left unspecified or otherwise set to their zero-values are assumed to not be
+// relevant, and do not participate in the endpoint search.
+type ApiCriteria struct {
+	// Name specifies the desired service catalog entry name.
+	Name string
+
+	// Region specifies the desired endpoint region.
+	Region string
+
+	// VersionId specifies the desired version of the endpoint.
+	// Note that this field is matched exactly, and is (at present)
+	// opaque to Gophercloud.  Thus, requesting a version 2
+	// endpoint will _not_ match a version 3 endpoint.
+	VersionId string
+
+	// The UrlChoice field inidicates whether or not gophercloud
+	// should use the public or internal endpoint URL if a
+	// candidate endpoint is found.
+	UrlChoice int
+}
+
+// The choices available for UrlChoice.  See the ApiCriteria structure for details.
+const (
+	PublicURL = iota
+	InternalURL
+)
+
+// Given a set of criteria to match on, locate the first candidate endpoint
+// in the provided service catalog.
+//
+// If nothing found, the result will be a zero-valued EntryEndpoint (all URLs
+// set to "").
+func FindFirstEndpointByCriteria(entries []CatalogEntry, ac ApiCriteria) EntryEndpoint {
+	rgn := strings.ToUpper(ac.Region)
+
+	for _, entry := range entries {
+		if (ac.Name != "") && (ac.Name != entry.Name) {
+			continue
+		}
+
+		for _, endpoint := range entry.Endpoints {
+			if (ac.Region != "") && (rgn != strings.ToUpper(endpoint.Region)) {
+				continue
+			}
+
+			if (ac.VersionId != "") && (ac.VersionId != endpoint.VersionId) {
+				continue
+			}
+
+			return endpoint
+		}
+	}
+	return EntryEndpoint{}
+}