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 }