gopkg.in/goose.v2@v2.0.1/testservices/openstackservice/openstack.go (about)

     1  package openstackservice
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"crypto/x509"
    11  	"gopkg.in/goose.v2/identity"
    12  	"gopkg.in/goose.v2/testservices/identityservice"
    13  	"gopkg.in/goose.v2/testservices/neutronmodel"
    14  	"gopkg.in/goose.v2/testservices/neutronservice"
    15  	"gopkg.in/goose.v2/testservices/novaservice"
    16  	"gopkg.in/goose.v2/testservices/swiftservice"
    17  )
    18  
    19  const (
    20  	Identity = "identity"
    21  	Neutron  = "neutron"
    22  	Nova     = "nova"
    23  	Swift    = "swift"
    24  )
    25  
    26  // Openstack provides an Openstack service double implementation.
    27  type Openstack struct {
    28  	Identity identityservice.IdentityService
    29  	// Keystone v3 supports serving both V2 and V3 at the same time
    30  	// this will intend to emulate that behavior.
    31  	FallbackIdentity identityservice.IdentityService
    32  	Nova             *novaservice.Nova
    33  	Neutron          *neutronservice.Neutron
    34  	Swift            *swiftservice.Swift
    35  	muxes            map[string]*http.ServeMux
    36  	servers          map[string]*httptest.Server
    37  	// base url of openstack endpoints, might be required to
    38  	// simmulate response contents such as the ones from
    39  	// identity discovery.
    40  	URLs map[string]string
    41  }
    42  
    43  func (openstack *Openstack) AddUser(user, secret, project, authDomain string) *identityservice.UserInfo {
    44  	uinfo := openstack.Identity.AddUser(user, secret, project, authDomain)
    45  	if openstack.FallbackIdentity != nil {
    46  		_ = openstack.FallbackIdentity.AddUser(user, secret, project, authDomain)
    47  	}
    48  	return uinfo
    49  }
    50  
    51  // New creates an instance of a full Openstack service double.
    52  // An initial user with the specified credentials is registered with the
    53  // identity service. This service double manages the httpServers necessary
    54  // for Neturon, Nova, Swift and Identity services
    55  func New(cred *identity.Credentials, authMode identity.AuthMode, useTLS bool) (*Openstack, []string) {
    56  	openstack, logMsgs := NewNoSwift(cred, authMode, useTLS)
    57  
    58  	var server *httptest.Server
    59  	if useTLS {
    60  		server = httptest.NewTLSServer(nil)
    61  	} else {
    62  		server = httptest.NewServer(nil)
    63  	}
    64  	logMsgs = append(logMsgs, "swift service started: "+server.URL)
    65  	openstack.muxes[Swift] = http.NewServeMux()
    66  	server.Config.Handler = openstack.muxes[Swift]
    67  	openstack.URLs[Swift] = server.URL
    68  	openstack.servers[Swift] = server
    69  
    70  	// Create the swift service using only the region base so we emulate real world deployments.
    71  	regionParts := strings.Split(cred.Region, ".")
    72  	baseRegion := regionParts[len(regionParts)-1]
    73  	openstack.Swift = swiftservice.New(openstack.URLs["swift"], "v1", cred.TenantName, baseRegion, openstack.Identity, openstack.FallbackIdentity)
    74  	// Create container and add image metadata endpoint so that product-streams URLs are included
    75  	// in the keystone catalog.
    76  	err := openstack.Swift.AddContainer("imagemetadata")
    77  	if err != nil {
    78  		panic(fmt.Errorf("setting up image metadata container: %v", err))
    79  	}
    80  	url := openstack.Swift.Endpoints()[0].PublicURL
    81  	serviceDef := identityservice.V2Service{
    82  		Name: "simplestreams",
    83  		Type: "product-streams",
    84  		Endpoints: []identityservice.Endpoint{
    85  			{PublicURL: url + "/imagemetadata", Region: cred.Region},
    86  		}}
    87  	service3Def := identityservice.V3Service{
    88  		Name:      "simplestreams",
    89  		Type:      "product-streams",
    90  		Endpoints: identityservice.NewV3Endpoints("", "", url+"/imagemetadata", cred.Region),
    91  	}
    92  
    93  	openstack.Identity.AddService(identityservice.Service{V2: serviceDef, V3: service3Def})
    94  	// Add public bucket endpoint so that juju-tools URLs are included in the keystone catalog.
    95  	serviceDef = identityservice.V2Service{
    96  		Name: "juju",
    97  		Type: "juju-tools",
    98  		Endpoints: []identityservice.Endpoint{
    99  			{PublicURL: url, Region: cred.Region},
   100  		}}
   101  	service3Def = identityservice.V3Service{
   102  		Name:      "juju",
   103  		Type:      "juju-tools",
   104  		Endpoints: identityservice.NewV3Endpoints("", "", url, cred.Region),
   105  	}
   106  
   107  	openstack.Identity.AddService(identityservice.Service{V2: serviceDef, V3: service3Def})
   108  	return openstack, logMsgs
   109  }
   110  
   111  // NewNoSwift creates an instance of a partial Openstack service double.
   112  // An initial user with the specified credentials is registered with the
   113  // identity service. This service double manages the httpServers necessary
   114  // for Nova and Identity services
   115  func NewNoSwift(cred *identity.Credentials, authMode identity.AuthMode, useTLS bool) (*Openstack, []string) {
   116  	var openstack Openstack
   117  	if authMode == identity.AuthKeyPair {
   118  		openstack = Openstack{
   119  			Identity: identityservice.NewKeyPair(),
   120  		}
   121  	} else if authMode == identity.AuthUserPassV3 {
   122  		openstack = Openstack{
   123  			Identity:         identityservice.NewV3UserPass(),
   124  			FallbackIdentity: identityservice.NewUserPass(),
   125  		}
   126  	} else {
   127  		openstack = Openstack{
   128  			Identity:         identityservice.NewUserPass(),
   129  			FallbackIdentity: identityservice.NewV3UserPass(),
   130  		}
   131  	}
   132  	domain := cred.ProjectDomain
   133  	if domain == "" {
   134  		domain = cred.UserDomain
   135  	}
   136  	if domain == "" {
   137  		domain = cred.Domain
   138  	}
   139  	if domain == "" {
   140  		domain = "default"
   141  	}
   142  	userInfo := openstack.AddUser(cred.User, cred.Secrets, cred.TenantName, domain)
   143  
   144  	if useTLS {
   145  		openstack.servers = map[string]*httptest.Server{
   146  			Identity: httptest.NewTLSServer(nil),
   147  			Neutron:  httptest.NewTLSServer(nil),
   148  			Nova:     httptest.NewTLSServer(nil),
   149  		}
   150  	} else {
   151  		openstack.servers = map[string]*httptest.Server{
   152  			Identity: httptest.NewServer(nil),
   153  			Neutron:  httptest.NewServer(nil),
   154  			Nova:     httptest.NewServer(nil),
   155  		}
   156  	}
   157  
   158  	openstack.muxes = map[string]*http.ServeMux{
   159  		Identity: http.NewServeMux(),
   160  		Neutron:  http.NewServeMux(),
   161  		Nova:     http.NewServeMux(),
   162  	}
   163  
   164  	for k, v := range openstack.servers {
   165  		v.Config.Handler = openstack.muxes[k]
   166  	}
   167  
   168  	cred.URL = openstack.servers[Identity].URL
   169  	openstack.URLs = make(map[string]string)
   170  	var logMsgs []string
   171  	for k, v := range openstack.servers {
   172  		openstack.URLs[k] = v.URL
   173  		logMsgs = append(logMsgs, k+" service started: "+openstack.URLs[k])
   174  	}
   175  
   176  	openstack.Nova = novaservice.New(openstack.URLs[Nova], "v2", userInfo.TenantId, cred.Region, openstack.Identity, openstack.FallbackIdentity)
   177  	openstack.Neutron = neutronservice.New(openstack.URLs[Neutron], "v2.0", userInfo.TenantId, cred.Region, openstack.Identity, openstack.FallbackIdentity)
   178  
   179  	return &openstack, logMsgs
   180  }
   181  
   182  // Certificate returns the x509 certificate of the specified service.
   183  func (openstack *Openstack) Certificate(serviceName string) (*x509.Certificate, error) {
   184  	service, ok := openstack.servers[serviceName]
   185  	if ok {
   186  		return service.Certificate(), nil
   187  	}
   188  	return nil, fmt.Errorf("%q not a running service", serviceName)
   189  }
   190  
   191  // UseNeutronNetworking sets up the openstack service to use neutron networking.
   192  func (openstack *Openstack) UseNeutronNetworking() {
   193  	// Neutron & Nova test doubles share a neutron data model for
   194  	// FloatingIPs, Networks & SecurityGroups
   195  	neutronModel := neutronmodel.New()
   196  	openstack.Nova.AddNeutronModel(neutronModel)
   197  	openstack.Neutron.AddNeutronModel(neutronModel)
   198  }
   199  
   200  // SetupHTTP attaches all the needed handlers to provide the HTTP API for the Openstack service..
   201  func (openstack *Openstack) SetupHTTP(mux *http.ServeMux) {
   202  	openstack.Identity.SetupHTTP(openstack.muxes[Identity])
   203  	// If there is a FallbackIdentity service also register its urls.
   204  	if openstack.FallbackIdentity != nil {
   205  		openstack.FallbackIdentity.SetupHTTP(openstack.muxes[Identity])
   206  	}
   207  	openstack.Neutron.SetupHTTP(openstack.muxes[Neutron])
   208  	openstack.Nova.SetupHTTP(openstack.muxes[Nova])
   209  	if openstack.Swift != nil {
   210  		openstack.Swift.SetupHTTP(openstack.muxes[Swift])
   211  	}
   212  
   213  	// Handle root calls to be able to return auth information.
   214  	// Neutron and Nova services must handle api version information.
   215  	// Swift has no list version api call to make
   216  	openstack.muxes["identity"].Handle("/", openstack)
   217  	openstack.Nova.SetupRootHandler(openstack.muxes[Nova])
   218  	openstack.Neutron.SetupRootHandler(openstack.muxes[Neutron])
   219  }
   220  
   221  // Stop closes the Openstack service double http Servers and clears the
   222  // related http handling
   223  func (openstack *Openstack) Stop() {
   224  	for _, v := range openstack.servers {
   225  		v.Config.Handler = nil
   226  		v.Close()
   227  	}
   228  	for k, _ := range openstack.muxes {
   229  		openstack.muxes[k] = nil
   230  	}
   231  }
   232  
   233  const authInformationBody = `{"versions": {"values": [{"status": "stable", ` +
   234  	`"updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", ` +
   235  	`"type": "application/vnd.openstack.identity-v3+json"}], "id": "v3.4", "links": ` +
   236  	`[{"href": "%s/v3/", "rel": "self"}]}, {"status": "stable", "updated": ` +
   237  	`"2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", ` +
   238  	`"type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", ` +
   239  	`"links": [{"href": "%s/v2.0/", "rel": "self"}, {"href": ` +
   240  	`"http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]}]}}`
   241  
   242  func (openstack *Openstack) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   243  	if r.URL.Path != "/" {
   244  		openstack.Nova.HandleRoot(w, r)
   245  		return
   246  	}
   247  	w.Header().Set("Content-Type", "application/json")
   248  	body := []byte(fmt.Sprintf(authInformationBody, openstack.URLs[Identity], openstack.URLs[Identity]))
   249  	// workaround for https://code.google.com/p/go/issues/detail?id=4454
   250  	w.Header().Set("Content-Length", strconv.Itoa(len(body)))
   251  	w.WriteHeader(http.StatusMultipleChoices)
   252  	w.Write(body)
   253  }