Dynamic Configuration Refactoring

This commit is contained in:
Ludovic Fernandez 2018-11-14 10:18:03 +01:00 committed by Traefiker Bot
parent d3ae88f108
commit a09dfa3ce1
452 changed files with 21023 additions and 9419 deletions

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-2017 Microsoft
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.

View file

@ -1,122 +0,0 @@
package appinsights
type Domain interface {
}
type domain struct {
Ver int `json:"ver"`
Properties map[string]string `json:"properties"`
}
type data struct {
BaseType string `json:"baseType"`
BaseData Domain `json:"baseData"`
}
type envelope struct {
Name string `json:"name"`
Time string `json:"time"`
IKey string `json:"iKey"`
Tags map[string]string `json:"tags"`
Data *data `json:"data"`
}
type DataPointType int
const (
Measurement DataPointType = iota
Aggregation
)
type DataPoint struct {
Name string `json:"name"`
Kind DataPointType `json:"kind"`
Value float32 `json:"value"`
Count int `json:"count"`
min float32 `json:"min"`
max float32 `json:"max"`
stdDev float32 `json:"stdDev"`
}
type metricData struct {
domain
Metrics []*DataPoint `json:"metrics"`
}
type eventData struct {
domain
Name string `json:"name"`
Measurements map[string]float32 `json:"measurements"`
}
type SeverityLevel int
const (
Verbose SeverityLevel = iota
Information
Warning
Error
Critical
)
type messageData struct {
domain
Message string `json:"message"`
SeverityLevel SeverityLevel `json:"severityLevel"`
}
type requestData struct {
domain
Id string `json:"id"`
Name string `json:"name"`
StartTime string `json:"startTime"` // yyyy-mm-ddThh:mm:ss.fffffff-hh:mm
Duration string `json:"duration"` // d:hh:mm:ss.fffffff
ResponseCode string `json:"responseCode"`
Success bool `json:"success"`
HttpMethod string `json:"httpMethod"`
Url string `json:"url"`
Measurements map[string]float32 `json:"measurements"`
}
type ContextTagKeys string
const (
ApplicationVersion ContextTagKeys = "ai.application.ver"
ApplicationBuild = "ai.application.build"
CloudRole = "ai.cloud.role"
CloudRoleInstance = "ai.cloud.roleInstance"
DeviceId = "ai.device.id"
DeviceIp = "ai.device.ip"
DeviceLanguage = "ai.device.language"
DeviceLocale = "ai.device.locale"
DeviceModel = "ai.device.model"
DeviceNetwork = "ai.device.network"
DeviceOEMName = "ai.device.oemName"
DeviceOS = "ai.device.os"
DeviceOSVersion = "ai.device.osVersion"
DeviceRoleInstance = "ai.device.roleInstance"
DeviceRoleName = "ai.device.roleName"
DeviceScreenResolution = "ai.device.screenResolution"
DeviceType = "ai.device.type"
DeviceMachineName = "ai.device.machineName"
LocationIp = "ai.location.ip"
OperationCorrelationVector = "ai.operation.correlationVector"
OperationId = "ai.operation.id"
OperationName = "ai.operation.name"
OperationParentId = "ai.operation.parentId"
OperationRootId = "ai.operation.rootId"
OperationSyntheticSource = "ai.operation.syntheticSource"
OperationIsSynthetic = "ai.operation.isSynthetic"
SessionId = "ai.session.id"
SessionIsFirst = "ai.session.isFirst"
SessionIsNew = "ai.session.isNew"
UserAccountAcquisitionDate = "ai.user.accountAcquisitionDate"
UserAccountId = "ai.user.accountId"
UserAgent = "ai.user.userAgent"
UserAuthUserId = "ai.user.authUserId"
UserId = "ai.user.id"
UserStoreRegion = "ai.user.storeRegion"
SampleRate = "ai.sample.sampleRate"
InternalSdkVersion = "ai.internal.sdkVersion"
InternalAgentVersion = "ai.internal.agentVersion"
)

View file

@ -1,132 +0,0 @@
package appinsights
import "time"
type TelemetryClient interface {
Context() TelemetryContext
InstrumentationKey() string
Channel() TelemetryChannel
IsEnabled() bool
SetIsEnabled(bool)
Track(Telemetry)
TrackEvent(string)
TrackEventTelemetry(*EventTelemetry)
TrackMetric(string, float32)
TrackMetricTelemetry(*MetricTelemetry)
TrackTrace(string)
TrackTraceTelemetry(*TraceTelemetry)
TrackRequest(string, string, string, time.Time, time.Duration, string, bool)
TrackRequestTelemetry(*RequestTelemetry)
}
type telemetryClient struct {
TelemetryConfiguration *TelemetryConfiguration
channel TelemetryChannel
context TelemetryContext
isEnabled bool
}
func NewTelemetryClient(iKey string) TelemetryClient {
return NewTelemetryClientFromConfig(NewTelemetryConfiguration(iKey))
}
func NewTelemetryClientFromConfig(config *TelemetryConfiguration) TelemetryClient {
channel := NewInMemoryChannel(config)
context := NewClientTelemetryContext()
return &telemetryClient{
TelemetryConfiguration: config,
channel: channel,
context: context,
isEnabled: true,
}
}
func (tc *telemetryClient) Context() TelemetryContext {
return tc.context
}
func (tc *telemetryClient) Channel() TelemetryChannel {
return tc.channel
}
func (tc *telemetryClient) InstrumentationKey() string {
return tc.TelemetryConfiguration.InstrumentationKey
}
func (tc *telemetryClient) IsEnabled() bool {
return tc.isEnabled
}
func (tc *telemetryClient) SetIsEnabled(isEnabled bool) {
tc.isEnabled = isEnabled
}
func (tc *telemetryClient) Track(item Telemetry) {
if tc.isEnabled {
iKey := tc.context.InstrumentationKey()
if len(iKey) == 0 {
iKey = tc.TelemetryConfiguration.InstrumentationKey
}
itemContext := item.Context().(*telemetryContext)
itemContext.iKey = iKey
clientContext := tc.context.(*telemetryContext)
for tagkey, tagval := range clientContext.tags {
if itemContext.tags[tagkey] == "" {
itemContext.tags[tagkey] = tagval
}
}
tc.channel.Send(item)
}
}
func (tc *telemetryClient) TrackEvent(name string) {
item := NewEventTelemetry(name)
tc.TrackEventTelemetry(item)
}
func (tc *telemetryClient) TrackEventTelemetry(event *EventTelemetry) {
var item Telemetry
item = event
tc.Track(item)
}
func (tc *telemetryClient) TrackMetric(name string, value float32) {
item := NewMetricTelemetry(name, value)
tc.TrackMetricTelemetry(item)
}
func (tc *telemetryClient) TrackMetricTelemetry(metric *MetricTelemetry) {
var item Telemetry
item = metric
tc.Track(item)
}
func (tc *telemetryClient) TrackTrace(message string) {
item := NewTraceTelemetry(message, Information)
tc.TrackTraceTelemetry(item)
}
func (tc *telemetryClient) TrackTraceTelemetry(trace *TraceTelemetry) {
var item Telemetry
item = trace
tc.Track(item)
}
func (tc *telemetryClient) TrackRequest(name, method, url string, timestamp time.Time, duration time.Duration, responseCode string, success bool) {
item := NewRequestTelemetry(name, method, url, timestamp, duration, responseCode, success)
tc.TrackRequestTelemetry(item)
}
func (tc *telemetryClient) TrackRequestTelemetry(request *RequestTelemetry) {
var item Telemetry
item = request
tc.Track(item)
}

View file

@ -1,11 +0,0 @@
package appinsights
// We need to mock out the clock for tests; we'll use this to do it.
import "code.cloudfoundry.org/clock"
var currentClock clock.Clock
func init() {
currentClock = clock.NewClock()
}

View file

@ -1,45 +0,0 @@
package appinsights
import (
"encoding/base64"
"math/rand"
"sync/atomic"
"time"
"unsafe"
)
type concurrentRandom struct {
channel chan string
random *rand.Rand
}
var randomGenerator *concurrentRandom
func newConcurrentRandom() *concurrentRandom {
source := rand.NewSource(time.Now().UnixNano())
return &concurrentRandom{
channel: make(chan string, 4),
random: rand.New(source),
}
}
func (generator *concurrentRandom) run() {
buf := make([]byte, 8)
for {
generator.random.Read(buf)
generator.channel <- base64.StdEncoding.EncodeToString(buf)
}
}
func randomId() string {
if randomGenerator == nil {
r := newConcurrentRandom()
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&randomGenerator)), unsafe.Pointer(nil), unsafe.Pointer(r)) {
go r.run()
} else {
close(r.channel)
}
}
return <-randomGenerator.channel
}

View file

@ -1,19 +0,0 @@
package appinsights
import "time"
type TelemetryConfiguration struct {
InstrumentationKey string
EndpointUrl string
MaxBatchSize int
MaxBatchInterval time.Duration
}
func NewTelemetryConfiguration(instrumentationKey string) *TelemetryConfiguration {
return &TelemetryConfiguration{
InstrumentationKey: instrumentationKey,
EndpointUrl: "https://dc.services.visualstudio.com/v2/track",
MaxBatchSize: 1024,
MaxBatchInterval: time.Duration(10) * time.Second,
}
}

View file

@ -1,228 +0,0 @@
package appinsights
import (
"fmt"
"time"
)
type Telemetry interface {
Timestamp() time.Time
Context() TelemetryContext
baseTypeName() string
baseData() Domain
SetProperty(string, string)
}
type BaseTelemetry struct {
timestamp time.Time
context TelemetryContext
}
type TraceTelemetry struct {
BaseTelemetry
data *messageData
}
func NewTraceTelemetry(message string, severityLevel SeverityLevel) *TraceTelemetry {
now := time.Now()
data := &messageData{
Message: message,
SeverityLevel: severityLevel,
}
data.Ver = 2
item := &TraceTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *TraceTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *TraceTelemetry) Context() TelemetryContext {
return item.context
}
func (item *TraceTelemetry) baseTypeName() string {
return "Message"
}
func (item *TraceTelemetry) baseData() Domain {
return item.data
}
func (item *TraceTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type EventTelemetry struct {
BaseTelemetry
data *eventData
}
func NewEventTelemetry(name string) *EventTelemetry {
now := time.Now()
data := &eventData{
Name: name,
}
data.Ver = 2
item := &EventTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *EventTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *EventTelemetry) Context() TelemetryContext {
return item.context
}
func (item *EventTelemetry) baseTypeName() string {
return "Event"
}
func (item *EventTelemetry) baseData() Domain {
return item.data
}
func (item *EventTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type MetricTelemetry struct {
BaseTelemetry
data *metricData
}
func NewMetricTelemetry(name string, value float32) *MetricTelemetry {
now := time.Now()
metric := &DataPoint{
Name: name,
Value: value,
Count: 1,
}
data := &metricData{
Metrics: make([]*DataPoint, 1),
}
data.Ver = 2
data.Metrics[0] = metric
item := &MetricTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *MetricTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *MetricTelemetry) Context() TelemetryContext {
return item.context
}
func (item *MetricTelemetry) baseTypeName() string {
return "Metric"
}
func (item *MetricTelemetry) baseData() Domain {
return item.data
}
func (item *MetricTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type RequestTelemetry struct {
BaseTelemetry
data *requestData
}
func NewRequestTelemetry(name, httpMethod, url string, timestamp time.Time, duration time.Duration, responseCode string, success bool) *RequestTelemetry {
now := time.Now()
data := &requestData{
Name: name,
StartTime: timestamp.Format(time.RFC3339Nano),
Duration: formatDuration(duration),
ResponseCode: responseCode,
Success: success,
HttpMethod: httpMethod,
Url: url,
Id: randomId(),
}
data.Ver = 2
item := &RequestTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *RequestTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *RequestTelemetry) Context() TelemetryContext {
return item.context
}
func (item *RequestTelemetry) baseTypeName() string {
return "Request"
}
func (item *RequestTelemetry) baseData() Domain {
return item.data
}
func (item *RequestTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
func formatDuration(d time.Duration) string {
ticks := int64(d/(time.Nanosecond*100)) % 10000000
seconds := int64(d/time.Second) % 60
minutes := int64(d/time.Minute) % 60
hours := int64(d/time.Hour) % 24
days := int64(d / (time.Hour * 24))
return fmt.Sprintf("%d.%02d:%02d:%02d.%07d", days, hours, minutes, seconds, ticks)
}

View file

@ -1,64 +0,0 @@
package appinsights
import "fmt"
type DiagnosticsMessageWriter interface {
Write(string)
appendListener(*diagnosticsMessageListener)
}
type diagnosticsMessageWriter struct {
listeners []chan string
}
type DiagnosticsMessageProcessor func(string)
type DiagnosticsMessageListener interface {
ProcessMessages(DiagnosticsMessageProcessor)
}
type diagnosticsMessageListener struct {
channel chan string
}
var diagnosticsWriter *diagnosticsMessageWriter = &diagnosticsMessageWriter{
listeners: make([]chan string, 0),
}
func NewDiagnosticsMessageListener() DiagnosticsMessageListener {
listener := &diagnosticsMessageListener{
channel: make(chan string),
}
diagnosticsWriter.appendListener(listener)
return listener
}
func (writer *diagnosticsMessageWriter) appendListener(listener *diagnosticsMessageListener) {
writer.listeners = append(writer.listeners, listener.channel)
}
func (writer *diagnosticsMessageWriter) Write(message string) {
for _, c := range writer.listeners {
c <- message
}
}
func (writer *diagnosticsMessageWriter) Printf(message string, args ...interface{}) {
// Don't bother with Sprintf if nobody is listening
if writer.hasListeners() {
writer.Write(fmt.Sprintf(message, args...))
}
}
func (writer *diagnosticsMessageWriter) hasListeners() bool {
return len(writer.listeners) > 0
}
func (listener *diagnosticsMessageListener) ProcessMessages(process DiagnosticsMessageProcessor) {
for {
message := <-listener.channel
process(message)
}
}

View file

@ -1,408 +0,0 @@
package appinsights
import (
"sync"
"time"
"code.cloudfoundry.org/clock"
)
var (
submit_retries = []time.Duration{time.Duration(10 * time.Second), time.Duration(30 * time.Second), time.Duration(60 * time.Second)}
)
type TelemetryBufferItems []Telemetry
type InMemoryChannel struct {
endpointAddress string
isDeveloperMode bool
collectChan chan Telemetry
controlChan chan *inMemoryChannelControl
batchSize int
batchInterval time.Duration
waitgroup sync.WaitGroup
throttle *throttleManager
transmitter transmitter
}
type inMemoryChannelControl struct {
// If true, flush the buffer.
flush bool
// If true, stop listening on the channel. (Flush is required if any events are to be sent)
stop bool
// If stopping and flushing, this specifies whether to retry submissions on error.
retry bool
// If retrying, what is the max time to wait before finishing up?
timeout time.Duration
// If specified, a message will be sent on this channel when all pending telemetry items have been submitted
callback chan struct{}
}
func NewInMemoryChannel(config *TelemetryConfiguration) *InMemoryChannel {
channel := &InMemoryChannel{
endpointAddress: config.EndpointUrl,
collectChan: make(chan Telemetry),
controlChan: make(chan *inMemoryChannelControl),
batchSize: config.MaxBatchSize,
batchInterval: config.MaxBatchInterval,
throttle: newThrottleManager(),
transmitter: newTransmitter(config.EndpointUrl),
}
go channel.acceptLoop()
return channel
}
func (channel *InMemoryChannel) EndpointAddress() string {
return channel.endpointAddress
}
func (channel *InMemoryChannel) Send(item Telemetry) {
if item != nil && channel.collectChan != nil {
channel.collectChan <- item
}
}
func (channel *InMemoryChannel) Flush() {
if channel.controlChan != nil {
channel.controlChan <- &inMemoryChannelControl{
flush: true,
}
}
}
func (channel *InMemoryChannel) Stop() {
if channel.controlChan != nil {
channel.controlChan <- &inMemoryChannelControl{
stop: true,
}
}
}
func (channel *InMemoryChannel) IsThrottled() bool {
return channel.throttle != nil && channel.throttle.IsThrottled()
}
func (channel *InMemoryChannel) Close(timeout ...time.Duration) <-chan struct{} {
if channel.controlChan != nil {
callback := make(chan struct{})
ctl := &inMemoryChannelControl{
stop: true,
flush: true,
retry: false,
callback: callback,
}
if len(timeout) > 0 {
ctl.retry = true
ctl.timeout = timeout[0]
}
channel.controlChan <- ctl
return callback
} else {
return nil
}
}
func (channel *InMemoryChannel) acceptLoop() {
channelState := newInMemoryChannelState(channel)
for !channelState.stopping {
channelState.start()
}
channelState.stop()
}
// Data shared between parts of a channel
type inMemoryChannelState struct {
channel *InMemoryChannel
stopping bool
buffer TelemetryBufferItems
retry bool
retryTimeout time.Duration
callback chan struct{}
timer clock.Timer
}
func newInMemoryChannelState(channel *InMemoryChannel) *inMemoryChannelState {
return &inMemoryChannelState{
channel: channel,
buffer: make(TelemetryBufferItems, 0, 16),
stopping: false,
timer: currentClock.NewTimer(channel.batchInterval),
}
}
// Part of channel accept loop: Initialize buffer and accept first message, handle controls.
func (state *inMemoryChannelState) start() bool {
if len(state.buffer) > 16 {
// Start out with the size of the previous buffer
state.buffer = make(TelemetryBufferItems, 0, cap(state.buffer))
} else if len(state.buffer) > 0 {
// Start out with at least 16 slots
state.buffer = make(TelemetryBufferItems, 0, 16)
}
// Wait for an event
select {
case event := <-state.channel.collectChan:
if event == nil {
// Channel closed? Not intercepted by Send()?
panic("Received nil event")
}
state.buffer = append(state.buffer, event)
case ctl := <-state.channel.controlChan:
// The buffer is empty, so there would be no point in flushing
state.channel.signalWhenDone(ctl.callback)
if ctl.stop {
state.stopping = true
return false
}
}
if len(state.buffer) == 0 {
return true
}
return state.waitToSend()
}
// Part of channel accept loop: Wait for buffer to fill, timeout to expire, or flush
func (state *inMemoryChannelState) waitToSend() bool {
// Things that are used by the sender if we receive a control message
state.retryTimeout = 0
state.retry = true
state.callback = nil
// Delay until timeout passes or buffer fills up
state.timer.Reset(state.channel.batchInterval)
for {
select {
case event := <-state.channel.collectChan:
if event == nil {
// Channel closed? Not intercepted by Send()?
panic("Received nil event")
}
state.buffer = append(state.buffer, event)
if len(state.buffer) >= state.channel.batchSize {
return state.send()
}
case ctl := <-state.channel.controlChan:
if ctl.stop {
state.stopping = true
state.retry = ctl.retry
if !ctl.flush {
// No flush? Just exit.
state.channel.signalWhenDone(ctl.callback)
return false
}
}
if ctl.flush {
state.retryTimeout = ctl.timeout
state.callback = ctl.callback
return state.send()
}
case _ = <-state.timer.C():
// Timeout expired
return state.send()
}
}
}
// Part of channel accept loop: Check and wait on throttle, submit pending telemetry
func (state *inMemoryChannelState) send() bool {
// Hold up transmission if we're being throttled
if !state.stopping && state.channel.throttle.IsThrottled() {
if !state.waitThrottle() {
// Stopped
return false
}
}
// Send
if len(state.buffer) > 0 {
state.channel.waitgroup.Add(1)
// If we have a callback, wait on the waitgroup now that it's
// incremented.
state.channel.signalWhenDone(state.callback)
go func(buffer TelemetryBufferItems, retry bool, retryTimeout time.Duration) {
defer state.channel.waitgroup.Done()
state.channel.transmitRetry(buffer, retry, retryTimeout)
}(state.buffer, state.retry, state.retryTimeout)
} else if state.callback != nil {
state.channel.signalWhenDone(state.callback)
}
return true
}
// Part of channel accept loop: Wait for throttle to expire while dropping messages
func (state *inMemoryChannelState) waitThrottle() bool {
// Channel is currently throttled. Once the buffer fills, messages will
// be lost... If we're exiting, then we'll just try to submit anyway. That
// request may be throttled and transmitRetry will perform the backoff correctly.
diagnosticsWriter.Write("Channel is throttled, events may be dropped.")
throttleDone := state.channel.throttle.NotifyWhenReady()
dropped := 0
defer diagnosticsWriter.Printf("Channel dropped %d events while throttled", dropped)
for {
select {
case <-throttleDone:
close(throttleDone)
return true
case event := <-state.channel.collectChan:
// If there's still room in the buffer, then go ahead and add it.
if len(state.buffer) < state.channel.batchSize {
state.buffer = append(state.buffer, event)
} else {
if dropped == 0 {
diagnosticsWriter.Write("Buffer is full, dropping further events.")
}
dropped++
}
case ctl := <-state.channel.controlChan:
if ctl.stop {
state.stopping = true
state.retry = ctl.retry
if !ctl.flush {
state.channel.signalWhenDone(ctl.callback)
return false
} else {
// Make an exception when stopping
return true
}
}
// Cannot flush
// TODO: Figure out what to do about callback?
if ctl.flush {
state.channel.signalWhenDone(ctl.callback)
}
}
}
}
// Part of channel accept loop: Clean up and close telemetry channel
func (state *inMemoryChannelState) stop() {
close(state.channel.collectChan)
close(state.channel.controlChan)
state.channel.collectChan = nil
state.channel.controlChan = nil
// Throttle can't close until transmitters are done using it.
state.channel.waitgroup.Wait()
state.channel.throttle.Stop()
state.channel.throttle = nil
}
func (channel *InMemoryChannel) transmitRetry(items TelemetryBufferItems, retry bool, retryTimeout time.Duration) {
payload := items.serialize()
retryTimeRemaining := retryTimeout
for _, wait := range submit_retries {
result, err := channel.transmitter.Transmit(payload, items)
if err == nil && result != nil && result.IsSuccess() {
return
}
if !retry {
diagnosticsWriter.Write("Refusing to retry telemetry submission (retry==false)")
return
}
// Check for success, determine if we need to retry anything
if result != nil {
if result.CanRetry() {
// Filter down to failed items
payload, items = result.GetRetryItems(payload, items)
if len(payload) == 0 || len(items) == 0 {
return
}
} else {
diagnosticsWriter.Write("Cannot retry telemetry submission")
return
}
// Check for throttling
if result.IsThrottled() {
if result.retryAfter != nil {
diagnosticsWriter.Printf("Channel is throttled until %s", *result.retryAfter)
channel.throttle.RetryAfter(*result.retryAfter)
} else {
// TODO: Pick a time
}
}
}
if retryTimeout > 0 {
// We're on a time schedule here. Make sure we don't try longer
// than we have been allowed.
if retryTimeRemaining < wait {
// One more chance left -- we'll wait the max time we can
// and then retry on the way out.
currentClock.Sleep(retryTimeRemaining)
break
} else {
// Still have time left to go through the rest of the regular
// retry schedule
retryTimeRemaining -= wait
}
}
diagnosticsWriter.Printf("Waiting %s to retry submission", wait)
currentClock.Sleep(wait)
// Wait if the channel is throttled and we're not on a schedule
if channel.IsThrottled() && retryTimeout == 0 {
diagnosticsWriter.Printf("Channel is throttled; extending wait time.")
ch := channel.throttle.NotifyWhenReady()
result := <-ch
close(ch)
if !result {
return
}
}
}
// One final try
_, err := channel.transmitter.Transmit(payload, items)
if err != nil {
diagnosticsWriter.Write("Gave up transmitting payload; exhausted retries")
}
}
func (channel *InMemoryChannel) signalWhenDone(callback chan struct{}) {
if callback != nil {
go func() {
channel.waitgroup.Wait()
close(callback)
}()
}
}

View file

@ -1,45 +0,0 @@
package appinsights
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
func (items TelemetryBufferItems) serialize() []byte {
var result bytes.Buffer
encoder := json.NewEncoder(&result)
for _, item := range items {
end := result.Len()
if err := encoder.Encode(prepare(item)); err != nil {
diagnosticsWriter.Write(fmt.Sprintf("Telemetry item failed to serialize: %s", err.Error()))
result.Truncate(end)
}
}
return result.Bytes()
}
func prepare(item Telemetry) *envelope {
data := &data{
BaseType: item.baseTypeName() + "Data",
BaseData: item.baseData(),
}
context := item.Context()
envelope := &envelope{
Name: "Microsoft.ApplicationInsights." + item.baseTypeName(),
Time: item.Timestamp().Format(time.RFC3339),
IKey: context.InstrumentationKey(),
Data: data,
}
if tcontext, ok := context.(*telemetryContext); ok {
envelope.Tags = tcontext.tags
}
return envelope
}

View file

@ -1,8 +0,0 @@
// Package appinsights provides an interface to submit telemetry to Application Insights.
// See more at https://azure.microsoft.com/en-us/services/application-insights/
package appinsights
const (
sdkName = "go"
Version = "0.3.1-pre"
)

View file

@ -1,47 +0,0 @@
package appinsights
import "time"
// Implementations of TelemetryChannel are responsible for queueing and
// periodically submitting telemetry items.
type TelemetryChannel interface {
// The address of the endpoint to which telemetry is sent
EndpointAddress() string
// Queues a single telemetry item
Send(Telemetry)
// Forces the current queue to be sent
Flush()
// Tears down the submission goroutines, closes internal channels.
// Any telemetry waiting to be sent is discarded. Further calls to
// Send() have undefined behavior. This is a more abrupt version of
// Close().
Stop()
// Returns true if this channel has been throttled by the data
// collector.
IsThrottled() bool
// Flushes and tears down the submission goroutine and closes
// internal channels. Returns a channel that is closed when all
// pending telemetry items have been submitted and it is safe to
// shut down without losing telemetry.
//
// If retryTimeout is specified and non-zero, then failed
// submissions will be retried until one succeeds or the timeout
// expires, whichever occurs first. A retryTimeout of zero
// indicates that failed submissions will be retried as usual. An
// omitted retryTimeout indicates that submissions should not be
// retried if they fail.
//
// Note that the returned channel may not be closed before
// retryTimeout even if it is specified. This is because
// retryTimeout only applies to the latest telemetry buffer. This
// may be typical for applications that submit a large amount of
// telemetry or are prone to being throttled. When exiting, you
// should select on the result channel and your own timer to avoid
// long delays.
Close(retryTimeout ...time.Duration) <-chan struct{}
}

View file

@ -1,400 +0,0 @@
package appinsights
import (
"os"
"runtime"
"strconv"
)
type TelemetryContext interface {
InstrumentationKey() string
loadDeviceContext()
Component() ComponentContext
Device() DeviceContext
Cloud() CloudContext
Session() SessionContext
User() UserContext
Operation() OperationContext
Location() LocationContext
}
type telemetryContext struct {
iKey string
tags map[string]string
}
type ComponentContext interface {
GetVersion() string
SetVersion(string)
}
type DeviceContext interface {
GetType() string
SetType(string)
GetId() string
SetId(string)
GetOperatingSystem() string
SetOperatingSystem(string)
GetOemName() string
SetOemName(string)
GetModel() string
SetModel(string)
GetNetworkType() string
SetNetworkType(string)
GetScreenResolution() string
SetScreenResolution(string)
GetLanguage() string
SetLanguage(string)
}
type CloudContext interface {
GetRoleName() string
SetRoleName(string)
GetRoleInstance() string
SetRoleInstance(string)
}
type SessionContext interface {
GetId() string
SetId(string)
GetIsFirst() bool
SetIsFirst(bool)
}
type UserContext interface {
GetId() string
SetId(string)
GetAccountId() string
SetAccountId(string)
GetUserAgent() string
SetUserAgent(string)
GetAuthenticatedUserId() string
SetAuthenticatedUserId(string)
}
type OperationContext interface {
GetId() string
SetId(string)
GetParentId() string
SetParentId(string)
GetCorrelationVector() string
SetCorrelationVector(string)
GetName() string
SetName(string)
GetSyntheticSource() string
SetSyntheticSource(string)
}
type LocationContext interface {
GetIp() string
SetIp(string)
}
func NewItemTelemetryContext() TelemetryContext {
context := &telemetryContext{
tags: make(map[string]string),
}
return context
}
func NewClientTelemetryContext() TelemetryContext {
context := &telemetryContext{
tags: make(map[string]string),
}
context.loadDeviceContext()
context.loadInternalContext()
return context
}
func (context *telemetryContext) InstrumentationKey() string {
return context.iKey
}
func (context *telemetryContext) loadDeviceContext() {
hostname, err := os.Hostname()
if err == nil {
context.tags[DeviceId] = hostname
context.tags[DeviceMachineName] = hostname
context.tags[DeviceRoleInstance] = hostname
}
context.tags[DeviceOS] = runtime.GOOS
}
func (context *telemetryContext) loadInternalContext() {
context.tags[InternalSdkVersion] = sdkName + ":" + Version
}
func (context *telemetryContext) Component() ComponentContext {
return &componentContext{context: context}
}
func (context *telemetryContext) Device() DeviceContext {
return &deviceContext{context: context}
}
func (context *telemetryContext) Cloud() CloudContext {
return &cloudContext{context: context}
}
func (context *telemetryContext) Session() SessionContext {
return &sessionContext{context: context}
}
func (context *telemetryContext) User() UserContext {
return &userContext{context: context}
}
func (context *telemetryContext) Operation() OperationContext {
return &operationContext{context: context}
}
func (context *telemetryContext) Location() LocationContext {
return &locationContext{context: context}
}
func (context *telemetryContext) getTagString(key ContextTagKeys) string {
if val, ok := context.tags[string(key)]; ok {
return val
}
return ""
}
func (context *telemetryContext) setTagString(key ContextTagKeys, value string) {
if value != "" {
context.tags[string(key)] = value
} else {
delete(context.tags, string(key))
}
}
func (context *telemetryContext) getTagBool(key ContextTagKeys) bool {
if val, ok := context.tags[string(key)]; ok {
if b, err := strconv.ParseBool(val); err != nil {
return b
}
}
return false
}
func (context *telemetryContext) setTagBool(key ContextTagKeys, value bool) {
if value {
context.tags[string(key)] = "true"
} else {
delete(context.tags, string(key))
}
}
type componentContext struct {
context *telemetryContext
}
type deviceContext struct {
context *telemetryContext
}
type cloudContext struct {
context *telemetryContext
}
type sessionContext struct {
context *telemetryContext
}
type userContext struct {
context *telemetryContext
}
type operationContext struct {
context *telemetryContext
}
type locationContext struct {
context *telemetryContext
}
func (context *componentContext) GetVersion() string {
return context.context.getTagString(ApplicationVersion)
}
func (context *componentContext) SetVersion(value string) {
context.context.setTagString(ApplicationVersion, value)
}
func (context *deviceContext) GetType() string {
return context.context.getTagString(DeviceType)
}
func (context *deviceContext) SetType(value string) {
context.context.setTagString(DeviceType, value)
}
func (context *deviceContext) GetId() string {
return context.context.getTagString(DeviceId)
}
func (context *deviceContext) SetId(value string) {
context.context.setTagString(DeviceId, value)
}
func (context *deviceContext) GetOperatingSystem() string {
return context.context.getTagString(DeviceOSVersion)
}
func (context *deviceContext) SetOperatingSystem(value string) {
context.context.setTagString(DeviceOSVersion, value)
}
func (context *deviceContext) GetOemName() string {
return context.context.getTagString(DeviceOEMName)
}
func (context *deviceContext) SetOemName(value string) {
context.context.setTagString(DeviceOEMName, value)
}
func (context *deviceContext) GetModel() string {
return context.context.getTagString(DeviceModel)
}
func (context *deviceContext) SetModel(value string) {
context.context.setTagString(DeviceModel, value)
}
func (context *deviceContext) GetNetworkType() string {
return context.context.getTagString(DeviceNetwork)
}
func (context *deviceContext) SetNetworkType(value string) {
context.context.setTagString(DeviceNetwork, value)
}
func (context *deviceContext) GetScreenResolution() string {
return context.context.getTagString(DeviceScreenResolution)
}
func (context *deviceContext) SetScreenResolution(value string) {
context.context.setTagString(DeviceScreenResolution, value)
}
func (context *deviceContext) GetLanguage() string {
return context.context.getTagString(DeviceLanguage)
}
func (context *deviceContext) SetLanguage(value string) {
context.context.setTagString(DeviceLanguage, value)
}
func (context *cloudContext) GetRoleName() string {
return context.context.getTagString(CloudRole)
}
func (context *cloudContext) SetRoleName(value string) {
context.context.setTagString(CloudRole, value)
}
func (context *cloudContext) GetRoleInstance() string {
return context.context.getTagString(CloudRoleInstance)
}
func (context *cloudContext) SetRoleInstance(value string) {
context.context.setTagString(CloudRoleInstance, value)
}
func (context *sessionContext) GetId() string {
return context.context.getTagString(SessionId)
}
func (context *sessionContext) SetId(value string) {
context.context.setTagString(SessionId, value)
}
func (context *sessionContext) GetIsFirst() bool {
return context.context.getTagBool(SessionIsFirst)
}
func (context *sessionContext) SetIsFirst(value bool) {
context.context.setTagBool(SessionIsFirst, value)
}
func (context *userContext) GetId() string {
return context.context.getTagString(UserId)
}
func (context *userContext) SetId(value string) {
context.context.setTagString(UserId, value)
}
func (context *userContext) GetAccountId() string {
return context.context.getTagString(UserAccountId)
}
func (context *userContext) SetAccountId(value string) {
context.context.setTagString(UserAccountId, value)
}
func (context *userContext) GetUserAgent() string {
return context.context.getTagString(UserAgent)
}
func (context *userContext) SetUserAgent(value string) {
context.context.setTagString(UserAgent, value)
}
func (context *userContext) GetAuthenticatedUserId() string {
return context.context.getTagString(UserAuthUserId)
}
func (context *userContext) SetAuthenticatedUserId(value string) {
context.context.setTagString(UserAuthUserId, value)
}
func (context *operationContext) GetId() string {
return context.context.getTagString(OperationId)
}
func (context *operationContext) SetId(value string) {
context.context.setTagString(OperationId, value)
}
func (context *operationContext) GetParentId() string {
return context.context.getTagString(OperationParentId)
}
func (context *operationContext) SetParentId(value string) {
context.context.setTagString(OperationParentId, value)
}
func (context *operationContext) GetCorrelationVector() string {
return context.context.getTagString(OperationCorrelationVector)
}
func (context *operationContext) SetCorrelationVector(value string) {
context.context.setTagString(OperationCorrelationVector, value)
}
func (context *operationContext) GetName() string {
return context.context.getTagString(OperationName)
}
func (context *operationContext) SetName(value string) {
context.context.setTagString(OperationName, value)
}
func (context *operationContext) GetSyntheticSource() string {
return context.context.getTagString(OperationSyntheticSource)
}
func (context *operationContext) SetSyntheticSource(value string) {
context.context.setTagString(OperationSyntheticSource, value)
}
func (context *locationContext) GetIp() string {
return context.context.getTagString(LocationIp)
}
func (context *locationContext) SetIp(value string) {
context.context.setTagString(LocationIp, value)
}

View file

@ -1,144 +0,0 @@
package appinsights
import (
"time"
)
type throttleManager struct {
msgs chan *throttleMessage
}
type throttleMessage struct {
query bool
wait bool
throttle bool
stop bool
timestamp time.Time
result chan bool
}
func newThrottleManager() *throttleManager {
result := &throttleManager{
msgs: make(chan *throttleMessage),
}
go result.run()
return result
}
func (throttle *throttleManager) RetryAfter(t time.Time) {
throttle.msgs <- &throttleMessage{
throttle: true,
timestamp: t,
}
}
func (throttle *throttleManager) IsThrottled() bool {
ch := make(chan bool)
throttle.msgs <- &throttleMessage{
query: true,
result: ch,
}
result := <-ch
close(ch)
return result
}
func (throttle *throttleManager) NotifyWhenReady() chan bool {
result := make(chan bool, 1)
throttle.msgs <- &throttleMessage{
wait: true,
result: result,
}
return result
}
func (throttle *throttleManager) Stop() {
result := make(chan bool)
throttle.msgs <- &throttleMessage{
stop: true,
result: result,
}
<-result
close(result)
}
func (throttle *throttleManager) run() {
for {
throttledUntil, ok := throttle.waitForThrottle()
if !ok {
break
}
if !throttle.waitForReady(throttledUntil) {
break
}
}
close(throttle.msgs)
}
func (throttle *throttleManager) waitForThrottle() (time.Time, bool) {
for {
msg := <-throttle.msgs
if msg.query {
msg.result <- false
} else if msg.wait {
msg.result <- true
} else if msg.stop {
return time.Time{}, false
} else if msg.throttle {
return msg.timestamp, true
}
}
}
func (throttle *throttleManager) waitForReady(throttledUntil time.Time) bool {
duration := throttledUntil.Sub(currentClock.Now())
if duration <= 0 {
return true
}
var notify []chan bool
// --- Throttled and waiting ---
t := currentClock.NewTimer(duration)
for {
select {
case <-t.C():
for _, n := range notify {
n <- true
}
return true
case msg := <-throttle.msgs:
if msg.query {
msg.result <- true
} else if msg.wait {
notify = append(notify, msg.result)
} else if msg.stop {
for _, n := range notify {
n <- false
}
msg.result <- true
return false
} else if msg.throttle {
if msg.timestamp.After(throttledUntil) {
throttledUntil = msg.timestamp
if !t.Stop() {
<-t.C()
}
t.Reset(throttledUntil.Sub(currentClock.Now()))
}
}
}
}
}

View file

@ -1,237 +0,0 @@
package appinsights
import (
"bytes"
"compress/gzip"
"encoding/json"
"io/ioutil"
"net/http"
"sort"
"time"
)
type transmitter interface {
Transmit(payload []byte, items TelemetryBufferItems) (*transmissionResult, error)
}
type httpTransmitter struct {
endpoint string
}
type transmissionResult struct {
statusCode int
retryAfter *time.Time
response *backendResponse
}
// Structures returned by data collector
type backendResponse struct {
ItemsReceived int `json:"itemsReceived"`
ItemsAccepted int `json:"itemsAccepted"`
Errors itemTransmissionResults `json:"errors"`
}
// This needs to be its own type because it implements sort.Interface
type itemTransmissionResults []*itemTransmissionResult
type itemTransmissionResult struct {
Index int `json:"index"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
}
const (
successResponse = 200
partialSuccessResponse = 206
requestTimeoutResponse = 408
tooManyRequestsResponse = 429
tooManyRequestsOverExtendedTimeResponse = 439
errorResponse = 500
serviceUnavailableResponse = 503
)
func newTransmitter(endpointAddress string) transmitter {
return &httpTransmitter{endpointAddress}
}
func (transmitter *httpTransmitter) Transmit(payload []byte, items TelemetryBufferItems) (*transmissionResult, error) {
diagnosticsWriter.Printf("----------- Transmitting %d items ---------", len(items))
startTime := time.Now()
// Compress the payload
var postBody bytes.Buffer
gzipWriter := gzip.NewWriter(&postBody)
if _, err := gzipWriter.Write(payload); err != nil {
diagnosticsWriter.Printf("Failed to compress the payload: %s", err.Error())
gzipWriter.Close()
return nil, err
}
gzipWriter.Close()
req, err := http.NewRequest("POST", transmitter.endpoint, &postBody)
if err != nil {
return nil, err
}
req.Header.Set("Content-Encoding", "gzip")
req.Header.Set("Content-Type", "application/x-json-stream")
req.Header.Set("Accept-Encoding", "gzip, deflate")
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
diagnosticsWriter.Printf("Failed to transmit telemetry: %s", err.Error())
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
diagnosticsWriter.Printf("Failed to read response from server: %s", err.Error())
return nil, err
}
duration := time.Since(startTime)
result := &transmissionResult{statusCode: resp.StatusCode}
// Grab Retry-After header
if retryAfterValue, ok := resp.Header[http.CanonicalHeaderKey("Retry-After")]; ok && len(retryAfterValue) == 1 {
if retryAfterTime, err := time.Parse(time.RFC1123, retryAfterValue[0]); err == nil {
result.retryAfter = &retryAfterTime
}
}
// Parse body, if possible
response := &backendResponse{}
if err := json.Unmarshal(body, &response); err == nil {
result.response = response
}
// Write diagnostics
if diagnosticsWriter.hasListeners() {
diagnosticsWriter.Printf("Telemetry transmitted in %s", duration)
diagnosticsWriter.Printf("Response: %d", result.statusCode)
if result.response != nil {
diagnosticsWriter.Printf("Items accepted/received: %d/%d", result.response.ItemsAccepted, result.response.ItemsReceived)
if len(result.response.Errors) > 0 {
diagnosticsWriter.Printf("Errors:")
for _, err := range result.response.Errors {
if err.Index < len(items) {
diagnosticsWriter.Printf("#%d - %d %s", err.Index, err.StatusCode, err.Message)
diagnosticsWriter.Printf("Telemetry item:\n\t%s", err.Index, string(items[err.Index:err.Index+1].serialize()))
}
}
}
}
}
return result, nil
}
func (result *transmissionResult) IsSuccess() bool {
return result.statusCode == successResponse ||
// Partial response but all items accepted
(result.statusCode == partialSuccessResponse &&
result.response != nil &&
result.response.ItemsReceived == result.response.ItemsAccepted)
}
func (result *transmissionResult) IsFailure() bool {
return result.statusCode != successResponse && result.statusCode != partialSuccessResponse
}
func (result *transmissionResult) CanRetry() bool {
if result.IsSuccess() {
return false
}
return result.statusCode == partialSuccessResponse ||
result.retryAfter != nil ||
(result.statusCode == requestTimeoutResponse ||
result.statusCode == serviceUnavailableResponse ||
result.statusCode == errorResponse ||
result.statusCode == tooManyRequestsResponse ||
result.statusCode == tooManyRequestsOverExtendedTimeResponse)
}
func (result *transmissionResult) IsPartialSuccess() bool {
return result.statusCode == partialSuccessResponse &&
result.response != nil &&
result.response.ItemsReceived != result.response.ItemsAccepted
}
func (result *transmissionResult) IsThrottled() bool {
return result.statusCode == tooManyRequestsResponse ||
result.statusCode == tooManyRequestsOverExtendedTimeResponse ||
result.retryAfter != nil
}
func (result *itemTransmissionResult) CanRetry() bool {
return result.StatusCode == requestTimeoutResponse ||
result.StatusCode == serviceUnavailableResponse ||
result.StatusCode == errorResponse ||
result.StatusCode == tooManyRequestsResponse ||
result.StatusCode == tooManyRequestsOverExtendedTimeResponse
}
func (result *transmissionResult) GetRetryItems(payload []byte, items TelemetryBufferItems) ([]byte, TelemetryBufferItems) {
if result.statusCode == partialSuccessResponse && result.response != nil {
// Make sure errors are ordered by index
sort.Sort(result.response.Errors)
var resultPayload bytes.Buffer
resultItems := make(TelemetryBufferItems, 0)
ptr := 0
idx := 0
// Find each retryable error
for _, responseResult := range result.response.Errors {
if responseResult.CanRetry() {
// Advance ptr to start of desired line
for ; idx < responseResult.Index && ptr < len(payload); ptr++ {
if payload[ptr] == '\n' {
idx++
}
}
startPtr := ptr
// Read to end of line
for ; idx == responseResult.Index && ptr < len(payload); ptr++ {
if payload[ptr] == '\n' {
idx++
}
}
// Copy item into output buffer
resultPayload.Write(payload[startPtr:ptr])
resultItems = append(resultItems, items[responseResult.Index])
}
}
return resultPayload.Bytes(), resultItems
} else if result.CanRetry() {
return payload, items
} else {
return payload[:0], items[:0]
}
}
// sort.Interface implementation for Errors[] list
func (results itemTransmissionResults) Len() int {
return len(results)
}
func (results itemTransmissionResults) Less(i, j int) bool {
return results[i].Index < results[j].Index
}
func (results itemTransmissionResults) Swap(i, j int) {
tmp := results[i]
results[i] = results[j]
results[j] = tmp
}

20
vendor/github.com/containous/alice/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Justinas Stankevicius
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.

116
vendor/github.com/containous/alice/chain.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
// Package alice provides a convenient way to chain http handlers.
package alice
import "net/http"
// Constructor A constructor for a piece of middleware.
// Some middleware use this constructor out of the box,
// so in most cases you can just pass somepackage.New
type Constructor func(http.Handler) (http.Handler, error)
// Chain acts as a list of http.Handler constructors.
// Chain is effectively immutable:
// once created, it will always hold
// the same set of constructors in the same order.
type Chain struct {
constructors []Constructor
}
// New creates a new chain,
// memorizing the given list of middleware constructors.
// New serves no other function,
// constructors are only called upon a call to Then().
func New(constructors ...Constructor) Chain {
return Chain{constructors: constructors}
}
// Then chains the middleware and returns the final http.Handler.
// New(m1, m2, m3).Then(h)
// is equivalent to:
// m1(m2(m3(h)))
// When the request comes in, it will be passed to m1, then m2, then m3
// and finally, the given handler
// (assuming every middleware calls the following one).
//
// A chain can be safely reused by calling Then() several times.
// stdStack := alice.New(ratelimitHandler, csrfHandler)
// indexPipe = stdStack.Then(indexHandler)
// authPipe = stdStack.Then(authHandler)
// Note that constructors are called on every call to Then()
// and thus several instances of the same middleware will be created
// when a chain is reused in this way.
// For proper middleware, this should cause no problems.
//
// Then() treats nil as http.DefaultServeMux.
func (c Chain) Then(h http.Handler) (http.Handler, error) {
if h == nil {
h = http.DefaultServeMux
}
for i := range c.constructors {
handler, err := c.constructors[len(c.constructors)-1-i](h)
if err != nil {
return nil, err
}
h = handler
}
return h, nil
}
// ThenFunc works identically to Then, but takes
// a HandlerFunc instead of a Handler.
//
// The following two statements are equivalent:
// c.Then(http.HandlerFunc(fn))
// c.ThenFunc(fn)
//
// ThenFunc provides all the guarantees of Then.
func (c Chain) ThenFunc(fn http.HandlerFunc) (http.Handler, error) {
if fn == nil {
return c.Then(nil)
}
return c.Then(fn)
}
// Append extends a chain, adding the specified constructors
// as the last ones in the request flow.
//
// Append returns a new chain, leaving the original one untouched.
//
// stdChain := alice.New(m1, m2)
// extChain := stdChain.Append(m3, m4)
// // requests in stdChain go m1 -> m2
// // requests in extChain go m1 -> m2 -> m3 -> m4
func (c Chain) Append(constructors ...Constructor) Chain {
newCons := make([]Constructor, 0, len(c.constructors)+len(constructors))
newCons = append(newCons, c.constructors...)
newCons = append(newCons, constructors...)
return Chain{newCons}
}
// Extend extends a chain by adding the specified chain
// as the last one in the request flow.
//
// Extend returns a new chain, leaving the original one untouched.
//
// stdChain := alice.New(m1, m2)
// ext1Chain := alice.New(m3, m4)
// ext2Chain := stdChain.Extend(ext1Chain)
// // requests in stdChain go m1 -> m2
// // requests in ext1Chain go m3 -> m4
// // requests in ext2Chain go m1 -> m2 -> m3 -> m4
//
// Another example:
// aHtmlAfterNosurf := alice.New(m2)
// aHtml := alice.New(m1, func(h http.Handler) http.Handler {
// csrf := nosurf.New(h)
// csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail))
// return csrf
// }).Extend(aHtmlAfterNosurf)
// // requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler
// // requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail
func (c Chain) Extend(chain Chain) Chain {
return c.Append(chain.constructors...)
}

View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Containous
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,306 +0,0 @@
package servicefabric
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/cenk/backoff"
"github.com/containous/flaeg"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/jjcollinge/logrus-appinsights"
sf "github.com/jjcollinge/servicefabric"
)
var _ provider.Provider = (*Provider)(nil)
const traefikServiceFabricExtensionKey = "Traefik"
const (
kindStateful = "Stateful"
kindStateless = "Stateless"
)
// Provider holds for configuration for the provider
type Provider struct {
provider.BaseProvider `mapstructure:",squash"`
ClusterManagementURL string `description:"Service Fabric API endpoint"`
APIVersion string `description:"Service Fabric API version" export:"true"`
RefreshSeconds flaeg.Duration `description:"Polling interval (in seconds)" export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
AppInsightsClientName string `description:"The client name, Identifies the cloud instance"`
AppInsightsKey string `description:"Application Insights Instrumentation Key"`
AppInsightsBatchSize int `description:"Number of trace lines per batch, optional"`
AppInsightsInterval flaeg.Duration `description:"The interval for sending data to Application Insights, optional"`
sfClient sfClient
}
// Init the provider
func (p *Provider) Init(constraints types.Constraints) error {
err := p.BaseProvider.Init(constraints)
if err != nil {
return err
}
if p.APIVersion == "" {
p.APIVersion = sf.DefaultAPIVersion
}
tlsConfig, err := p.TLS.CreateTLSConfig()
if err != nil {
return err
}
p.sfClient, err = sf.NewClient(http.DefaultClient, p.ClusterManagementURL, p.APIVersion, tlsConfig)
if err != nil {
return err
}
if p.RefreshSeconds <= 0 {
p.RefreshSeconds = flaeg.Duration(10 * time.Second)
}
if p.AppInsightsClientName != "" && p.AppInsightsKey != "" {
if p.AppInsightsBatchSize == 0 {
p.AppInsightsBatchSize = 10
}
if p.AppInsightsInterval == 0 {
p.AppInsightsInterval = flaeg.Duration(5 * time.Second)
}
createAppInsightsHook(p.AppInsightsClientName, p.AppInsightsKey, p.AppInsightsBatchSize, p.AppInsightsInterval)
}
return nil
}
// Provide allows the ServiceFabric provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
return p.updateConfig(configurationChan, pool, time.Duration(p.RefreshSeconds))
}
func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, pollInterval time.Duration) error {
pool.Go(func(stop chan bool) {
operation := func() error {
ticker := time.NewTicker(pollInterval)
for range ticker.C {
select {
case shouldStop := <-stop:
if shouldStop {
ticker.Stop()
return nil
}
default:
log.Info("Checking service fabric config")
}
configuration, err := p.getConfiguration()
if err != nil {
return err
}
configurationChan <- types.ConfigMessage{
ProviderName: "servicefabric",
Configuration: configuration,
}
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Provider connection error: %v; retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Cannot connect to Provider: %v", err)
}
})
return nil
}
func (p *Provider) getConfiguration() (*types.Configuration, error) {
services, err := getClusterServices(p.sfClient)
if err != nil {
return nil, err
}
return p.buildConfiguration(services)
}
func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
apps, err := sfClient.GetApplications()
if err != nil {
return nil, err
}
var results []ServiceItemExtended
for _, app := range apps.Items {
services, err := sfClient.GetServices(app.ID)
if err != nil {
return nil, err
}
for _, service := range services.Items {
item := ServiceItemExtended{
ServiceItem: service,
Application: app,
}
if labels, err := getLabels(sfClient, &service, &app); err != nil {
log.Error(err)
} else {
item.Labels = labels
}
if partitions, err := sfClient.GetPartitions(app.ID, service.ID); err != nil {
log.Error(err)
} else {
for _, partition := range partitions.Items {
partitionExt := PartitionItemExtended{PartitionItem: partition}
if isStateful(item) {
partitionExt.Replicas = getValidReplicas(sfClient, app, service, partition)
} else if isStateless(item) {
partitionExt.Instances = getValidInstances(sfClient, app, service, partition)
} else {
log.Errorf("Unsupported service kind %s in service %s", partition.ServiceKind, service.Name)
continue
}
item.Partitions = append(item.Partitions, partitionExt)
}
}
results = append(results, item)
}
}
return results, nil
}
func getValidReplicas(sfClient sfClient, app sf.ApplicationItem, service sf.ServiceItem, partition sf.PartitionItem) []sf.ReplicaItem {
var validReplicas []sf.ReplicaItem
if replicas, err := sfClient.GetReplicas(app.ID, service.ID, partition.PartitionInformation.ID); err != nil {
log.Error(err)
} else {
for _, instance := range replicas.Items {
if isHealthy(instance.ReplicaItemBase) && hasHTTPEndpoint(instance.ReplicaItemBase) {
validReplicas = append(validReplicas, instance)
}
}
}
return validReplicas
}
func getValidInstances(sfClient sfClient, app sf.ApplicationItem, service sf.ServiceItem, partition sf.PartitionItem) []sf.InstanceItem {
var validInstances []sf.InstanceItem
if instances, err := sfClient.GetInstances(app.ID, service.ID, partition.PartitionInformation.ID); err != nil {
log.Error(err)
} else {
for _, instance := range instances.Items {
if isHealthy(instance.ReplicaItemBase) && hasHTTPEndpoint(instance.ReplicaItemBase) {
validInstances = append(validInstances, instance)
}
}
}
return validInstances
}
func isHealthy(instanceData *sf.ReplicaItemBase) bool {
return instanceData != nil && (instanceData.ReplicaStatus == "Ready" && instanceData.HealthState != "Error")
}
func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
_, err := getReplicaDefaultEndpoint(instanceData)
return err == nil
}
func getReplicaDefaultEndpoint(replicaData *sf.ReplicaItemBase) (string, error) {
endpoints, err := decodeEndpointData(replicaData.Address)
if err != nil {
return "", err
}
var defaultHTTPEndpoint string
for _, v := range endpoints {
if strings.Contains(v, "http") {
defaultHTTPEndpoint = v
break
}
}
if len(defaultHTTPEndpoint) == 0 {
return "", errors.New("no default endpoint found")
}
return defaultHTTPEndpoint, nil
}
func decodeEndpointData(endpointData string) (map[string]string, error) {
var endpointsMap map[string]map[string]string
if endpointData == "" {
return nil, errors.New("endpoint data is empty")
}
err := json.Unmarshal([]byte(endpointData), &endpointsMap)
if err != nil {
return nil, err
}
endpoints, endpointsExist := endpointsMap["Endpoints"]
if !endpointsExist {
return nil, errors.New("endpoint doesn't exist in endpoint data")
}
return endpoints, nil
}
func isStateful(service ServiceItemExtended) bool {
return service.ServiceKind == kindStateful
}
func isStateless(service ServiceItemExtended) bool {
return service.ServiceKind == kindStateless
}
// Return a set of labels from the Extension and Property manager
// Allow Extension labels to disable importing labels from the property manager.
func getLabels(sfClient sfClient, service *sf.ServiceItem, app *sf.ApplicationItem) (map[string]string, error) {
labels, err := sfClient.GetServiceExtensionMap(service, app, traefikServiceFabricExtensionKey)
if err != nil {
log.Errorf("Error retrieving serviceExtensionMap: %v", err)
return nil, err
}
if label.GetBoolValue(labels, traefikSFEnableLabelOverrides, traefikSFEnableLabelOverridesDefault) {
if exists, properties, err := sfClient.GetProperties(service.ID); err == nil && exists {
for key, value := range properties {
labels[key] = value
}
}
}
return labels, nil
}
func createAppInsightsHook(appInsightsClientName string, instrumentationKey string, maxBatchSize int, interval flaeg.Duration) {
hook, err := logrus_appinsights.New(appInsightsClientName, logrus_appinsights.Config{
InstrumentationKey: instrumentationKey,
MaxBatchSize: maxBatchSize, // optional
MaxBatchInterval: time.Duration(interval), // optional
})
if err != nil || hook == nil {
panic(err)
}
// ignore fields
hook.AddIgnore("private")
log.AddHook(hook)
}

View file

@ -1,174 +0,0 @@
package servicefabric
import (
"errors"
"strings"
"text/template"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
sf "github.com/jjcollinge/servicefabric"
)
func (p *Provider) buildConfiguration(services []ServiceItemExtended) (*types.Configuration, error) {
var sfFuncMap = template.FuncMap{
// Services
"getServices": getServices,
"hasLabel": hasService,
"getLabelValue": getServiceStringLabel,
"getLabelsWithPrefix": getServiceLabelsWithPrefix,
"isPrimary": isPrimary,
"isStateful": isStateful,
"isStateless": isStateless,
"isEnabled": getFuncBoolLabel(label.TraefikEnable, false),
"getBackendName": getBackendName,
"getDefaultEndpoint": getDefaultEndpoint,
"getNamedEndpoint": getNamedEndpoint, // FIXME unused
"getApplicationParameter": getApplicationParameter, // FIXME unused
"doesAppParamContain": doesAppParamContain, // FIXME unused
"filterServicesByLabelValue": filterServicesByLabelValue, // FIXME unused
// Backend functions
"getWeight": getFuncServiceIntLabel(label.TraefikWeight, label.DefaultWeight),
"getProtocol": getFuncServiceStringLabel(label.TraefikProtocol, label.DefaultProtocol),
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer,
// Frontend Functions
"getPriority": getFuncServiceIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": getFuncServiceBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncServiceSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncServiceSliceStringLabel(label.TraefikFrontendAuthBasic),
"getFrontendRules": getFuncServiceLabelWithPrefix(label.TraefikFrontendRule),
"getWhiteList": getWhiteList,
"getHeaders": getHeaders,
"getRedirect": getRedirect,
// SF Service Grouping
"getGroupedServices": getFuncServicesGroupedByLabel(traefikSFGroupName),
"getGroupedWeight": getFuncServiceStringLabel(traefikSFGroupWeight, "1"),
}
templateObjects := struct {
Services []ServiceItemExtended
}{
Services: services,
}
return p.GetConfiguration(tmpl, sfFuncMap, templateObjects)
}
func isPrimary(instance replicaInstance) bool {
_, data := instance.GetReplicaData()
return data.ReplicaRole == "Primary"
}
func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string {
return provider.Normalize(service.Name + partition.PartitionInformation.ID)
}
func getDefaultEndpoint(instance replicaInstance) string {
id, data := instance.GetReplicaData()
endpoint, err := getReplicaDefaultEndpoint(data)
if err != nil {
log.Warnf("No default endpoint for replica %s in service %s endpointData: %s", id, data.Address, err)
return ""
}
return endpoint
}
func getNamedEndpoint(instance replicaInstance, endpointName string) string {
id, data := instance.GetReplicaData()
endpoint, err := getReplicaNamedEndpoint(data, endpointName)
if err != nil {
log.Warnf("No names endpoint of %s for replica %s in endpointData: %s. Error: %v", endpointName, id, data.Address, err)
return ""
}
return endpoint
}
func getReplicaNamedEndpoint(replicaData *sf.ReplicaItemBase, endpointName string) (string, error) {
endpoints, err := decodeEndpointData(replicaData.Address)
if err != nil {
return "", err
}
endpoint, exists := endpoints[endpointName]
if !exists {
return "", errors.New("endpoint doesn't exist")
}
return endpoint, nil
}
func getApplicationParameter(app sf.ApplicationItem, key string) string {
for _, param := range app.Parameters {
if param.Key == key {
return param.Value
}
}
log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name)
return ""
}
func getServices(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended {
result := map[string][]ServiceItemExtended{}
for _, service := range services {
if value, exists := service.Labels[key]; exists {
if matchingServices, hasKeyAlready := result[value]; hasKeyAlready {
result[value] = append(matchingServices, service)
} else {
result[value] = []ServiceItemExtended{service}
}
}
}
return result
}
func doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool {
value := getApplicationParameter(app, key)
return strings.Contains(value, shouldContain)
}
func filterServicesByLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended {
var srvWithLabel []ServiceItemExtended
for _, service := range services {
value, exists := service.Labels[key]
if exists && value == expectedValue {
srvWithLabel = append(srvWithLabel, service)
}
}
return srvWithLabel
}
func getHeaders(service ServiceItemExtended) *types.Headers {
return label.GetHeaders(service.Labels)
}
func getWhiteList(service ServiceItemExtended) *types.WhiteList {
return label.GetWhiteList(service.Labels)
}
func getRedirect(service ServiceItemExtended) *types.Redirect {
return label.GetRedirect(service.Labels)
}
func getMaxConn(service ServiceItemExtended) *types.MaxConn {
return label.GetMaxConn(service.Labels)
}
func getHealthCheck(service ServiceItemExtended) *types.HealthCheck {
return label.GetHealthCheck(service.Labels)
}
func getCircuitBreaker(service ServiceItemExtended) *types.CircuitBreaker {
return label.GetCircuitBreaker(service.Labels)
}
func getLoadBalancer(service ServiceItemExtended) *types.LoadBalancer {
return label.GetLoadBalancer(service.Labels)
}

View file

@ -1,75 +0,0 @@
package servicefabric
import (
"strings"
"github.com/containous/traefik/provider/label"
)
// SF Specific Traefik Labels
const (
traefikSFGroupName = "traefik.servicefabric.groupname"
traefikSFGroupWeight = "traefik.servicefabric.groupweight"
traefikSFEnableLabelOverrides = "traefik.servicefabric.enablelabeloverrides"
traefikSFEnableLabelOverridesDefault = true
)
func getFuncBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool {
return func(service ServiceItemExtended) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
func getServiceStringLabel(service ServiceItemExtended, labelName string, defaultValue string) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
func getFuncServiceStringLabel(labelName string, defaultValue string) func(service ServiceItemExtended) string {
return func(service ServiceItemExtended) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
func getFuncServiceIntLabel(labelName string, defaultValue int) func(service ServiceItemExtended) int {
return func(service ServiceItemExtended) int {
return label.GetIntValue(service.Labels, labelName, defaultValue)
}
}
func getFuncServiceBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool {
return func(service ServiceItemExtended) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
func getFuncServiceSliceStringLabel(labelName string) func(service ServiceItemExtended) []string {
return func(service ServiceItemExtended) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
func hasService(service ServiceItemExtended, labelName string) bool {
return label.Has(service.Labels, labelName)
}
func getFuncServiceLabelWithPrefix(labelName string) func(service ServiceItemExtended) map[string]string {
return func(service ServiceItemExtended) map[string]string {
return getServiceLabelsWithPrefix(service, labelName)
}
}
func getFuncServicesGroupedByLabel(labelName string) func(services []ServiceItemExtended) map[string][]ServiceItemExtended {
return func(services []ServiceItemExtended) map[string][]ServiceItemExtended {
return getServices(services, labelName)
}
}
func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string {
results := make(map[string]string)
for k, v := range service.Labels {
if strings.HasPrefix(k, prefix) {
results[k] = v
}
}
return results
}

View file

@ -1,228 +0,0 @@
package servicefabric
const tmpl = `
[backends]
{{range $aggName, $aggServices := getGroupedServices .Services }}
[backends."{{ $aggName }}"]
{{range $service := $aggServices }}
{{range $partition := $service.Partitions }}
{{range $instance := $partition.Instances }}
[backends."{{ $aggName }}".servers."{{ $service.ID }}-{{ $instance.ID }}"]
url = "{{ getDefaultEndpoint $instance }}"
weight = {{ getGroupedWeight $service }}
{{end}}
{{end}}
{{end}}
{{end}}
{{range $service := .Services }}
{{if isEnabled $service }}
{{range $partition := $service.Partitions }}
{{if isStateless $service }}
{{ $backendName := $service.Name }}
[backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $service }}
{{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{ $loadBalancer := getLoadBalancer $service }}
{{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
sticky = {{ $loadBalancer.Sticky }}
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
{{end}}
{{end}}
{{ $maxConn := getMaxConn $service }}
{{if $maxConn }}
[backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{ $healthCheck := getHealthCheck $service }}
{{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
port = {{ $healthCheck.Port }}
interval = "{{ $healthCheck.Interval }}"
hostname = "{{ $healthCheck.Hostname }}"
{{if $healthCheck.Headers }}
[backends."{{ $backendName }}".healthCheck.headers]
{{range $k, $v := $healthCheck.Headers }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $instance := $partition.Instances}}
[backends."{{ $service.Name }}".servers."{{ $instance.ID }}"]
url = "{{ getDefaultEndpoint $instance }}"
weight = {{ getWeight $service }}
{{end}}
{{else if isStateful $service}}
{{range $replica := $partition.Replicas}}
{{if isPrimary $replica}}
{{ $backendName := getBackendName $service $partition }}
[backends."{{ $backendName }}".servers."{{ $replica.ID }}"]
url = "{{ getDefaultEndpoint $replica }}"
weight = 1
[backends."{{$backendName}}".LoadBalancer]
method = "drr"
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $groupName, $groupServices := getGroupedServices .Services }}
{{ $service := index $groupServices 0 }}
[frontends."{{ $groupName }}"]
backend = "{{ $groupName }}"
priority = 50
{{range $key, $value := getFrontendRules $service }}
[frontends."{{ $groupName }}".routes."{{ $key }}"]
rule = "{{ $value }}"
{{end}}
{{end}}
{{range $service := .Services }}
{{if isEnabled $service }}
{{ $frontendName := $service.Name }}
{{if isStateless $service }}
[frontends."frontend-{{ $frontendName }}"]
backend = "{{ $service.Name }}"
passHostHeader = {{ getPassHostHeader $service }}
passTLSCert = {{ getPassTLSCert $service }}
priority = {{ getPriority $service }}
{{ $entryPoints := getEntryPoints $service }}
{{if $entryPoints }}
entryPoints = [{{range $entryPoints }}
"{{.}}",
{{end}}]
{{end}}
{{ $basicAuth := getBasicAuth $service }}
{{if $basicAuth }}
basicAuth = [{{range $basicAuth }}
"{{.}}",
{{end}}]
{{end}}
{{ $whitelist := getWhiteList $service }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
"{{.}}",
{{end}}]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
regex = "{{ $redirect.Regex }}"
replacement = "{{ $redirect.Replacement }}"
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $headers := getHeaders $service }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }}
SSLHost = "{{ $headers.SSLHost }}"
STSSeconds = {{ $headers.STSSeconds }}
STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }}
STSPreload = {{ $headers.STSPreload }}
ForceSTSHeader = {{ $headers.ForceSTSHeader }}
FrameDeny = {{ $headers.FrameDeny }}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
{{if $headers.AllowedHosts }}
AllowedHosts = [{{range $headers.AllowedHosts }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.HostsProxyHeaders }}
HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.CustomRequestHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.customRequestHeaders]
{{range $k, $v := $headers.CustomRequestHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.CustomResponseHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.customResponseHeaders]
{{range $k, $v := $headers.CustomResponseHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.SSLProxyHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.SSLProxyHeaders]
{{range $k, $v := $headers.SSLProxyHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $key, $value := getFrontendRules $service }}
[frontends."frontend-{{ $frontendName }}".routes."{{ $key }}"]
rule = "{{ $value }}"
{{end}}
{{else if isStateful $service }}
{{range $partition := $service.Partitions }}
{{ $partitionId := $partition.PartitionInformation.ID }}
{{ $rule := getLabelValue $service (print "traefik.frontend.rule.partition." $partitionId) "" }}
{{if $rule }}
[frontends."{{ $service.Name }}/{{ $partitionId }}"]
backend = "{{ getBackendName $service $partition }}"
[frontends."{{ $service.Name }}/{{ $partitionId }}".routes.default]
rule = "{{ $rule }}"
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
`

View file

@ -1,42 +0,0 @@
package servicefabric
import (
sf "github.com/jjcollinge/servicefabric"
)
// ServiceItemExtended provides a flattened view
// of the service with details of the application
// it belongs too and the replicas/partitions
type ServiceItemExtended struct {
sf.ServiceItem
Application sf.ApplicationItem
Partitions []PartitionItemExtended
Labels map[string]string
}
// PartitionItemExtended provides a flattened view
// of a services partitions
type PartitionItemExtended struct {
sf.PartitionItem
Replicas []sf.ReplicaItem
Instances []sf.InstanceItem
}
// sfClient is an interface for Service Fabric client's to implement.
// This is purposely a subset of the total Service Fabric API surface.
type sfClient interface {
GetApplications() (*sf.ApplicationItemsPage, error)
GetServices(appName string) (*sf.ServiceItemsPage, error)
GetPartitions(appName, serviceName string) (*sf.PartitionItemsPage, error)
GetReplicas(appName, serviceName, partitionName string) (*sf.ReplicaItemsPage, error)
GetInstances(appName, serviceName, partitionName string) (*sf.InstanceItemsPage, error)
GetServiceExtensionMap(service *sf.ServiceItem, app *sf.ApplicationItem, extensionKey string) (map[string]string, error)
GetServiceLabels(service *sf.ServiceItem, app *sf.ApplicationItem, prefix string) (map[string]string, error)
GetProperties(name string) (bool, map[string]string, error)
}
// replicaInstance interface provides a unified interface
// over replicas and instances
type replicaInstance interface {
GetReplicaData() (string, *sf.ReplicaItemBase)
}

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 jjcollinge
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.

View file

@ -1,11 +0,0 @@
package logrus_appinsights
import "time"
// Config for Application Insights settings
type Config struct {
InstrumentationKey string
EndpointUrl string
MaxBatchSize int
MaxBatchInterval time.Duration
}

View file

@ -1,173 +0,0 @@
package logrus_appinsights
import (
"encoding/json"
"fmt"
"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"github.com/sirupsen/logrus"
)
var defaultLevels = []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
}
var levelMap = map[logrus.Level]appinsights.SeverityLevel{
logrus.PanicLevel: appinsights.Critical,
logrus.FatalLevel: appinsights.Critical,
logrus.ErrorLevel: appinsights.Error,
logrus.WarnLevel: appinsights.Warning,
logrus.InfoLevel: appinsights.Information,
}
// AppInsightsHook is a logrus hook for Application Insights
type AppInsightsHook struct {
client appinsights.TelemetryClient
async bool
levels []logrus.Level
ignoreFields map[string]struct{}
filters map[string]func(interface{}) interface{}
}
// New returns an initialised logrus hook for Application Insights
func New(name string, conf Config) (*AppInsightsHook, error) {
if conf.InstrumentationKey == "" {
return nil, fmt.Errorf("InstrumentationKey is required and missing from configuration")
}
telemetryConf := appinsights.NewTelemetryConfiguration(conf.InstrumentationKey)
if conf.MaxBatchSize != 0 {
telemetryConf.MaxBatchSize = conf.MaxBatchSize
}
if conf.MaxBatchInterval != 0 {
telemetryConf.MaxBatchInterval = conf.MaxBatchInterval
}
if conf.EndpointUrl != "" {
telemetryConf.EndpointUrl = conf.EndpointUrl
}
telemetryClient := appinsights.NewTelemetryClientFromConfig(telemetryConf)
if name != "" {
telemetryClient.Context().Cloud().SetRoleName(name)
}
return &AppInsightsHook{
client: telemetryClient,
levels: defaultLevels,
ignoreFields: make(map[string]struct{}),
filters: make(map[string]func(interface{}) interface{}),
}, nil
}
// NewWithAppInsightsConfig returns an initialised logrus hook for Application Insights
func NewWithAppInsightsConfig(name string, conf *appinsights.TelemetryConfiguration) (*AppInsightsHook, error) {
if conf == nil {
return nil, fmt.Errorf("Nil configuration provided")
}
if conf.InstrumentationKey == "" {
return nil, fmt.Errorf("InstrumentationKey is required in configuration")
}
telemetryClient := appinsights.NewTelemetryClientFromConfig(conf)
if name != "" {
telemetryClient.Context().Cloud().SetRoleName(name)
}
return &AppInsightsHook{
client: telemetryClient,
levels: defaultLevels,
ignoreFields: make(map[string]struct{}),
filters: make(map[string]func(interface{}) interface{}),
}, nil
}
// Levels returns logging level to fire this hook.
func (hook *AppInsightsHook) Levels() []logrus.Level {
return hook.levels
}
// SetLevels sets logging level to fire this hook.
func (hook *AppInsightsHook) SetLevels(levels []logrus.Level) {
hook.levels = levels
}
// SetAsync sets async flag for sending logs asynchronously.
// If use this true, Fire() does not return error.
func (hook *AppInsightsHook) SetAsync(async bool) {
hook.async = async
}
// AddIgnore adds field name to ignore.
func (hook *AppInsightsHook) AddIgnore(name string) {
hook.ignoreFields[name] = struct{}{}
}
// AddFilter adds a custom filter function.
func (hook *AppInsightsHook) AddFilter(name string, fn func(interface{}) interface{}) {
hook.filters[name] = fn
}
// Fire is invoked by logrus and sends log data to Application Insights.
func (hook *AppInsightsHook) Fire(entry *logrus.Entry) error {
if !hook.async {
return hook.fire(entry)
}
// async - fire and forget
go hook.fire(entry)
return nil
}
func (hook *AppInsightsHook) fire(entry *logrus.Entry) error {
trace, err := hook.buildTrace(entry)
if err != nil {
return err
}
hook.client.TrackTraceTelemetry(trace)
return nil
}
func (hook *AppInsightsHook) buildTrace(entry *logrus.Entry) (*appinsights.TraceTelemetry, error) {
// Add the message as a field if it isn't already
if _, ok := entry.Data["message"]; !ok {
entry.Data["message"] = entry.Message
}
level := levelMap[entry.Level]
trace := appinsights.NewTraceTelemetry(entry.Message, level)
if trace == nil {
return nil, fmt.Errorf("Could not create telemetry trace with entry %+v", entry)
}
for k, v := range entry.Data {
if _, ok := hook.ignoreFields[k]; ok {
continue
}
if fn, ok := hook.filters[k]; ok {
v = fn(v) // apply custom filter
} else {
v = formatData(v) // use default formatter
}
vStr := fmt.Sprintf("%v", v)
trace.SetProperty(k, vStr)
}
trace.SetProperty("source_level", entry.Level.String())
trace.SetProperty("source_timestamp", entry.Time.String())
return trace, nil
}
// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
switch value := value.(type) {
case json.Marshaler:
return value
case error:
return value.Error()
case fmt.Stringer:
return value.String()
default:
return value
}
}
func stringPtr(str string) *string {
return &str
}

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,20 +0,0 @@
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

@ -1,392 +0,0 @@
// 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
}

View file

@ -1,199 +0,0 @@
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"`
}
}