-
Notifications
You must be signed in to change notification settings - Fork 7
/
servicefabric.go
392 lines (333 loc) · 11.5 KB
/
servicefabric.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
// Package servicefabric is an opinionated Service Fabric client written in Golang
package servicefabric
import (
"crypto/tls"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
// DefaultAPIVersion is a default Service Fabric REST API version
const DefaultAPIVersion = "3.0"
// Client for Service Fabric.
// This is purposely a subset of the total Service Fabric API surface.
type Client struct {
// endpoint Service Fabric cluster management endpoint
endpoint string
// apiVersion Service Fabric API version
apiVersion string
// httpClient HTTP client
httpClient *http.Client
}
// NewClient returns a new provider client that can query the
// Service Fabric management API externally or internally
func NewClient(httpClient *http.Client, endpoint, apiVersion string, tlsConfig *tls.Config) (*Client, error) {
if endpoint == "" {
return nil, errors.New("endpoint missing for httpClient configuration")
}
if apiVersion == "" {
apiVersion = DefaultAPIVersion
}
if tlsConfig != nil {
tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient
tlsConfig.BuildNameToCertificate()
httpClient.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}
return &Client{
endpoint: endpoint,
apiVersion: apiVersion,
httpClient: httpClient,
}, nil
}
// GetApplications returns all the registered applications
// within the Service Fabric cluster.
func (c Client) GetApplications() (*ApplicationItemsPage, error) {
var aggregateAppItemsPages ApplicationItemsPage
var continueToken string
for {
res, err := c.getHTTP("Applications/", withContinue(continueToken))
if err != nil {
return nil, err
}
var appItemsPage ApplicationItemsPage
err = json.Unmarshal(res, &appItemsPage)
if err != nil {
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
aggregateAppItemsPages.Items = append(aggregateAppItemsPages.Items, appItemsPage.Items...)
continueToken = getString(appItemsPage.ContinuationToken)
if continueToken == "" {
break
}
}
return &aggregateAppItemsPages, nil
}
// GetServices returns all the services associated
// with a Service Fabric application.
func (c Client) GetServices(appName string) (*ServiceItemsPage, error) {
var aggregateServiceItemsPages ServiceItemsPage
var continueToken string
for {
res, err := c.getHTTP("Applications/"+appName+"/$/GetServices", withContinue(continueToken))
if err != nil {
return nil, err
}
var servicesItemsPage ServiceItemsPage
err = json.Unmarshal(res, &servicesItemsPage)
if err != nil {
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
aggregateServiceItemsPages.Items = append(aggregateServiceItemsPages.Items, servicesItemsPage.Items...)
continueToken = getString(servicesItemsPage.ContinuationToken)
if continueToken == "" {
break
}
}
return &aggregateServiceItemsPages, nil
}
// GetPartitions returns all the partitions associated
// with a Service Fabric service.
func (c Client) GetPartitions(appName, serviceName string) (*PartitionItemsPage, error) {
var aggregatePartitionItemsPages PartitionItemsPage
var continueToken string
for {
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/"
res, err := c.getHTTP(basePath, withContinue(continueToken))
if err != nil {
return nil, err
}
var partitionsItemsPage PartitionItemsPage
err = json.Unmarshal(res, &partitionsItemsPage)
if err != nil {
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
aggregatePartitionItemsPages.Items = append(aggregatePartitionItemsPages.Items, partitionsItemsPage.Items...)
continueToken = getString(partitionsItemsPage.ContinuationToken)
if continueToken == "" {
break
}
}
return &aggregatePartitionItemsPages, nil
}
// GetInstances returns all the instances associated
// with a stateless Service Fabric partition.
func (c Client) GetInstances(appName, serviceName, partitionName string) (*InstanceItemsPage, error) {
var aggregateInstanceItemsPages InstanceItemsPage
var continueToken string
for {
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/" + partitionName + "/$/GetReplicas"
res, err := c.getHTTP(basePath, withContinue(continueToken))
if err != nil {
return nil, err
}
var instanceItemsPage InstanceItemsPage
err = json.Unmarshal(res, &instanceItemsPage)
if err != nil {
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
aggregateInstanceItemsPages.Items = append(aggregateInstanceItemsPages.Items, instanceItemsPage.Items...)
continueToken = getString(instanceItemsPage.ContinuationToken)
if continueToken == "" {
break
}
}
return &aggregateInstanceItemsPages, nil
}
// GetReplicas returns all the replicas associated
// with a stateful Service Fabric partition.
func (c Client) GetReplicas(appName, serviceName, partitionName string) (*ReplicaItemsPage, error) {
var aggregateReplicaItemsPages ReplicaItemsPage
var continueToken string
for {
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/" + partitionName + "/$/GetReplicas"
res, err := c.getHTTP(basePath, withContinue(continueToken))
if err != nil {
return nil, err
}
var replicasItemsPage ReplicaItemsPage
err = json.Unmarshal(res, &replicasItemsPage)
if err != nil {
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
aggregateReplicaItemsPages.Items = append(aggregateReplicaItemsPages.Items, replicasItemsPage.Items...)
continueToken = getString(replicasItemsPage.ContinuationToken)
if continueToken == "" {
break
}
}
return &aggregateReplicaItemsPages, nil
}
// GetServiceExtension returns all the extensions specified
// in a Service's manifest file. If the XML schema does not
// map to the provided interface, the default type interface will
// be returned.
func (c Client) GetServiceExtension(appType, applicationVersion, serviceTypeName, extensionKey string, response interface{}) error {
res, err := c.getHTTP("ApplicationTypes/"+appType+"/$/GetServiceTypes", withParam("ApplicationTypeVersion", applicationVersion))
if err != nil {
return fmt.Errorf("error requesting service extensions: %v", err)
}
var serviceTypes []ServiceType
err = json.Unmarshal(res, &serviceTypes)
if err != nil {
return fmt.Errorf("could not deserialise JSON response: %+v", err)
}
for _, serviceTypeInfo := range serviceTypes {
if serviceTypeInfo.ServiceTypeDescription.ServiceTypeName == serviceTypeName {
for _, extension := range serviceTypeInfo.ServiceTypeDescription.Extensions {
if strings.EqualFold(extension.Key, extensionKey) {
err = xml.Unmarshal([]byte(extension.Value), &response)
if err != nil {
return fmt.Errorf("could not deserialise extension's XML value: %+v", err)
}
return nil
}
}
}
}
return nil
}
// GetServiceExtensionMap returns all the extension xml specified
// in a Service's manifest file into (which must conform to ServiceExtensionLabels)
// a map[string]string
func (c Client) GetServiceExtensionMap(service *ServiceItem, app *ApplicationItem, extensionKey string) (map[string]string, error) {
extensionData := ServiceExtensionLabels{}
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, extensionKey, &extensionData)
if err != nil {
return nil, err
}
labels := map[string]string{}
if extensionData.Label != nil {
for _, label := range extensionData.Label {
labels[label.Key] = label.Value
}
}
return labels, nil
}
// GetProperties uses the Property Manager API to retrieve
// string properties from a name as a dictionary
// Property name is the path to the properties you would like to list.
// for example a serviceID
func (c Client) GetProperties(name string) (bool, map[string]string, error) {
nameExists, err := c.nameExists(name)
if err != nil {
return false, nil, err
}
if !nameExists {
return false, nil, nil
}
properties := make(map[string]string)
var continueToken string
for {
res, err := c.getHTTP("Names/"+name+"/$/GetProperties", withContinue(continueToken), withParam("IncludeValues", "true"))
if err != nil {
return false, nil, err
}
var propertiesListPage PropertiesListPage
err = json.Unmarshal(res, &propertiesListPage)
if err != nil {
return false, nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
}
for _, property := range propertiesListPage.Properties {
if property.Value.Kind != "String" {
continue
}
properties[property.Name] = property.Value.Data
}
continueToken = propertiesListPage.ContinuationToken
if continueToken == "" {
break
}
}
return true, properties, nil
}
// GetServiceLabels add labels from service manifest extensions and properties manager
// expects extension xml in <Label key="key">value</Label>
//
// Deprecated: Use GetProperties and GetServiceExtensionMap instead.
func (c Client) GetServiceLabels(service *ServiceItem, app *ApplicationItem, prefix string) (map[string]string, error) {
extensionData := ServiceExtensionLabels{}
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, prefix, &extensionData)
if err != nil {
return nil, err
}
prefixPeriod := prefix + "."
labels := map[string]string{}
if extensionData.Label != nil {
for _, label := range extensionData.Label {
if strings.HasPrefix(label.Key, prefixPeriod) {
labelKey := strings.Replace(label.Key, prefixPeriod, "", -1)
labels[labelKey] = label.Value
}
}
}
exists, properties, err := c.GetProperties(service.ID)
if err != nil {
return nil, err
}
if exists {
for k, v := range properties {
if strings.HasPrefix(k, prefixPeriod) {
labelKey := strings.Replace(k, prefixPeriod, "", -1)
labels[labelKey] = v
}
}
}
return labels, nil
}
func (c Client) nameExists(propertyName string) (bool, error) {
res, err := c.getHTTPRaw("Names/" + propertyName)
// Get http will return error for any non 200 response code.
if err != nil {
return false, err
}
return res.StatusCode == http.StatusOK, nil
}
func (c Client) getHTTP(basePath string, paramsFuncs ...queryParamsFunc) ([]byte, error) {
if c.httpClient == nil {
return nil, errors.New("invalid http client provided")
}
url := c.getURL(basePath, paramsFuncs...)
res, err := c.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to connect to Service Fabric server %+v on %s", err, url)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Service Fabric responded with error code %s to request %s with body %v", res.Status, url, res.Body)
}
if res.Body == nil {
return nil, errors.New("empty response body from Service Fabric")
}
defer res.Body.Close()
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
return nil, fmt.Errorf("failed to read response body from Service Fabric response %+v", readErr)
}
return body, nil
}
func (c Client) getHTTPRaw(basePath string) (*http.Response, error) {
if c.httpClient == nil {
return nil, fmt.Errorf("invalid http client provided")
}
url := c.getURL(basePath)
res, err := c.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to connect to Service Fabric server %+v on %s", err, url)
}
return res, nil
}
func (c Client) getURL(basePath string, paramsFuncs ...queryParamsFunc) string {
params := []string{"api-version=" + c.apiVersion}
for _, paramsFunc := range paramsFuncs {
params = paramsFunc(params)
}
return fmt.Sprintf("%s/%s?%s", c.endpoint, basePath, strings.Join(params, "&"))
}
func getString(str *string) string {
if str == nil {
return ""
}
return *str
}