Add Service Fabric Provider

This commit is contained in:
Lawrence Gripper 2017-11-27 13:26:04 +00:00 committed by Traefiker
parent 9f6f637527
commit 39c1cc1b3c
21 changed files with 1624 additions and 18 deletions

21
vendor/github.com/jjcollinge/servicefabric/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Joni Collinge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
vendor/github.com/jjcollinge/servicefabric/query.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
package servicefabric
type queryParamsFunc func(params []string) []string
func withContinue(token string) queryParamsFunc {
if len(token) == 0 {
return noOp
}
return withParam("continue", token)
}
func withParam(name, value string) queryParamsFunc {
return func(params []string) []string {
return append(params, name+"="+value)
}
}
func noOp(params []string) []string {
return params
}

View file

@ -0,0 +1,367 @@
// 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"
)
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
}
// GetProperties uses the Property Manager API to retrieve
// string properties from a name as a dictionary
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>
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
}

199
vendor/github.com/jjcollinge/servicefabric/types.go generated vendored Normal file
View file

@ -0,0 +1,199 @@
package servicefabric
import "encoding/xml"
// ApplicationItemsPage encapsulates the paged response
// model for Applications in the Service Fabric API
type ApplicationItemsPage struct {
ContinuationToken *string `json:"ContinuationToken"`
Items []ApplicationItem `json:"Items"`
}
// AppParameter Application parameter
type AppParameter struct {
Key string `json:"Key"`
Value string `json:"Value"`
}
// ApplicationItem encapsulates the embedded model for
// ApplicationItems within the ApplicationItemsPage model
type ApplicationItem struct {
HealthState string `json:"HealthState"`
ID string `json:"Id"`
Name string `json:"Name"`
Parameters []*AppParameter `json:"Parameters"`
Status string `json:"Status"`
TypeName string `json:"TypeName"`
TypeVersion string `json:"TypeVersion"`
}
// ServiceItemsPage encapsulates the paged response
// model for Services in the Service Fabric API
type ServiceItemsPage struct {
ContinuationToken *string `json:"ContinuationToken"`
Items []ServiceItem `json:"Items"`
}
// ServiceItem encapsulates the embedded model for
// ServiceItems within the ServiceItemsPage model
type ServiceItem struct {
HasPersistedState bool `json:"HasPersistedState"`
HealthState string `json:"HealthState"`
ID string `json:"Id"`
IsServiceGroup bool `json:"IsServiceGroup"`
ManifestVersion string `json:"ManifestVersion"`
Name string `json:"Name"`
ServiceKind string `json:"ServiceKind"`
ServiceStatus string `json:"ServiceStatus"`
TypeName string `json:"TypeName"`
}
// PartitionItemsPage encapsulates the paged response
// model for PartitionItems in the Service Fabric API
type PartitionItemsPage struct {
ContinuationToken *string `json:"ContinuationToken"`
Items []PartitionItem `json:"Items"`
}
// PartitionItem encapsulates the service information
// returned for each PartitionItem under the service
type PartitionItem struct {
CurrentConfigurationEpoch ConfigurationEpoch `json:"CurrentConfigurationEpoch"`
HealthState string `json:"HealthState"`
MinReplicaSetSize int64 `json:"MinReplicaSetSize"`
PartitionInformation PartitionInformation `json:"PartitionInformation"`
PartitionStatus string `json:"PartitionStatus"`
ServiceKind string `json:"ServiceKind"`
TargetReplicaSetSize int64 `json:"TargetReplicaSetSize"`
}
// ConfigurationEpoch Partition configuration epoch
type ConfigurationEpoch struct {
ConfigurationVersion string `json:"ConfigurationVersion"`
DataLossVersion string `json:"DataLossVersion"`
}
// PartitionInformation Partition information
type PartitionInformation struct {
HighKey string `json:"HighKey"`
ID string `json:"Id"`
LowKey string `json:"LowKey"`
ServicePartitionKind string `json:"ServicePartitionKind"`
}
// ReplicaItemBase shared data used
// in both replicas and instances
type ReplicaItemBase struct {
Address string `json:"Address"`
HealthState string `json:"HealthState"`
LastInBuildDurationInSeconds string `json:"LastInBuildDurationInSeconds"`
NodeName string `json:"NodeName"`
ReplicaRole string `json:"ReplicaRole"`
ReplicaStatus string `json:"ReplicaStatus"`
ServiceKind string `json:"ServiceKind"`
}
// ReplicaItemsPage encapsulates the response
// model for Replicas in the Service Fabric API
type ReplicaItemsPage struct {
ContinuationToken *string `json:"ContinuationToken"`
Items []ReplicaItem `json:"Items"`
}
// ReplicaItem holds replica specific data
type ReplicaItem struct {
*ReplicaItemBase
ID string `json:"ReplicaId"`
}
// GetReplicaData returns replica data
func (m *ReplicaItem) GetReplicaData() (string, *ReplicaItemBase) {
return m.ID, m.ReplicaItemBase
}
// InstanceItemsPage encapsulates the response
// model for Instances in the Service Fabric API
type InstanceItemsPage struct {
ContinuationToken *string `json:"ContinuationToken"`
Items []InstanceItem `json:"Items"`
}
// InstanceItem hold instance specific data
type InstanceItem struct {
*ReplicaItemBase
ID string `json:"InstanceId"`
}
// GetReplicaData returns replica data from an instance
func (m *InstanceItem) GetReplicaData() (string, *ReplicaItemBase) {
return m.ID, m.ReplicaItemBase
}
// ServiceType encapsulates the response model for
// Service types in the Service Fabric API
type ServiceType struct {
ServiceTypeDescription ServiceTypeDescription `json:"ServiceTypeDescription"`
ServiceManifestVersion string `json:"ServiceManifestVersion"`
ServiceManifestName string `json:"ServiceManifestName"`
IsServiceGroup bool `json:"IsServiceGroup"`
}
// ServiceTypeDescription Service Type Description
type ServiceTypeDescription struct {
IsStateful bool `json:"IsStateful"`
ServiceTypeName string `json:"ServiceTypeName"`
PlacementConstraints string `json:"PlacementConstraints"`
HasPersistedState bool `json:"HasPersistedState"`
Kind string `json:"Kind"`
Extensions []KeyValuePair `json:"Extensions"`
LoadMetrics []interface{} `json:"LoadMetrics"`
ServicePlacementPolicies []interface{} `json:"ServicePlacementPolicies"`
}
// PropertiesListPage encapsulates the response model for
// PagedPropertyInfoList in the Service Fabric API
type PropertiesListPage struct {
ContinuationToken string `json:"ContinuationToken"`
IsConsistent bool `json:"IsConsistent"`
Properties []Property `json:"Properties"`
}
// Property Paged Property Info
type Property struct {
Metadata Metadata `json:"Metadata"`
Name string `json:"Name"`
Value PropValue `json:"Value"`
}
// Metadata Property Metadata
type Metadata struct {
CustomTypeID string `json:"CustomTypeId"`
LastModifiedUtcTimestamp string `json:"LastModifiedUtcTimestamp"`
Parent string `json:"Parent"`
SequenceNumber string `json:"SequenceNumber"`
SizeInBytes int64 `json:"SizeInBytes"`
TypeID string `json:"TypeId"`
}
// PropValue Property value
type PropValue struct {
Data string `json:"Data"`
Kind string `json:"Kind"`
}
// KeyValuePair represents a key value pair structure
type KeyValuePair struct {
Key string `json:"Key"`
Value string `json:"Value"`
}
// ServiceExtensionLabels provides the structure for
// deserialising the XML document used to store labels in an Extension
type ServiceExtensionLabels struct {
XMLName xml.Name `xml:"Labels"`
Label []struct {
XMLName xml.Name `xml:"Label"`
Value string `xml:",chardata"`
Key string `xml:"Key,attr"`
}
}