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