File: openstack.go

package info (click to toggle)
golang-gopkg-goose.v1 0.0~git20170406.3228e4f-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,116 kB
  • sloc: python: 160; makefile: 3
file content (239 lines) | stat: -rw-r--r-- 8,922 bytes parent folder | download | duplicates (2)
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
package openstackservice

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"

	"gopkg.in/goose.v1/identity"
	"gopkg.in/goose.v1/testservices/identityservice"
	"gopkg.in/goose.v1/testservices/neutronmodel"
	"gopkg.in/goose.v1/testservices/neutronservice"
	"gopkg.in/goose.v1/testservices/novaservice"
	"gopkg.in/goose.v1/testservices/swiftservice"
)

// 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
}

// 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)
}