Release Argo Tunnel Client 2018.6.1

This commit is contained in:
cloudflare-warp-bot 2018-06-12 15:41:49 +00:00
parent 4ca5e622ca
commit 4268bc1a9c
6 changed files with 167 additions and 118 deletions

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/cloudflare/cloudflared/metallog"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
@ -46,7 +47,7 @@ type MuxerConfig struct {
// The minimum number of heartbeats to send before terminating the connection. // The minimum number of heartbeats to send before terminating the connection.
MaxHeartbeats uint64 MaxHeartbeats uint64
// Logger to use // Logger to use
Logger *log.Logger Logger *log.Entry
} }
type Muxer struct { type Muxer struct {
@ -95,7 +96,7 @@ func Handshake(
config.Timeout = defaultTimeout config.Timeout = defaultTimeout
} }
if config.Logger == nil { if config.Logger == nil {
config.Logger = log.New() config.Logger = metallog.New().WithFields(log.Fields{})
} }
// Initialise connection state fields // Initialise connection state fields
m := &Muxer{ m := &Muxer{
@ -259,10 +260,9 @@ func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time.
} }
func (m *Muxer) Serve(ctx context.Context) error { func (m *Muxer) Serve(ctx context.Context) error {
logger := m.config.Logger.WithField("name", m.config.Name)
errGroup, _ := errgroup.WithContext(ctx) errGroup, _ := errgroup.WithContext(ctx)
errGroup.Go(func() error { errGroup.Go(func() error {
err := m.muxReader.run(logger) err := m.muxReader.run(m.config.Logger)
m.explicitShutdown.Fuse(false) m.explicitShutdown.Fuse(false)
m.r.Close() m.r.Close()
m.abort() m.abort()
@ -270,7 +270,7 @@ func (m *Muxer) Serve(ctx context.Context) error {
}) })
errGroup.Go(func() error { errGroup.Go(func() error {
err := m.muxWriter.run(logger) err := m.muxWriter.run(m.config.Logger)
m.explicitShutdown.Fuse(false) m.explicitShutdown.Fuse(false)
m.w.Close() m.w.Close()
m.abort() m.abort()
@ -278,7 +278,7 @@ func (m *Muxer) Serve(ctx context.Context) error {
}) })
errGroup.Go(func() error { errGroup.Go(func() error {
err := m.muxMetricsUpdater.run(logger) err := m.muxMetricsUpdater.run(m.config.Logger)
return err return err
}) })

13
metallog/metallog.go Normal file
View File

@ -0,0 +1,13 @@
package metallog
import (
tunnellog "github.com/cloudflare/cloudflared/log"
log "github.com/sirupsen/logrus"
)
// New creates a logger formatted for JSON output
func New() *log.Logger {
logger := log.New()
logger.Formatter = &tunnellog.JSONFormatter{}
return logger
}

View File

@ -23,13 +23,13 @@ import (
raven "github.com/getsentry/raven-go" raven "github.com/getsentry/raven-go"
"github.com/pkg/errors" "github.com/pkg/errors"
_ "github.com/prometheus/client_golang/prometheus" _ "github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
rpc "zombiezen.com/go/capnproto2/rpc" rpc "zombiezen.com/go/capnproto2/rpc"
) )
const ( const (
dialTimeout = 15 * time.Second dialTimeout = 15 * time.Second
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
TagHeaderNamePrefix = "Cf-Warp-Tag-" TagHeaderNamePrefix = "Cf-Warp-Tag-"
DuplicateConnectionError = "EDUPCONN" DuplicateConnectionError = "EDUPCONN"
) )
@ -53,8 +53,8 @@ type TunnelConfig struct {
HTTPTransport http.RoundTripper HTTPTransport http.RoundTripper
Metrics *TunnelMetrics Metrics *TunnelMetrics
MetricsUpdateFreq time.Duration MetricsUpdateFreq time.Duration
ProtocolLogger *logrus.Logger ProtocolLogger *log.Logger
Logger *logrus.Logger Logger *log.Logger
IsAutoupdated bool IsAutoupdated bool
GracePeriod time.Duration GracePeriod time.Duration
RunFromTerminal bool RunFromTerminal bool
@ -196,9 +196,9 @@ func ServeTunnel(
tags["ha"] = connectionTag tags["ha"] = connectionTag
// Returns error from parsing the origin URL or handshake errors // Returns error from parsing the origin URL or handshake errors
handler, originLocalIP, err := NewTunnelHandler(ctx, logger, config, addr.String(), connectionID) handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr.String(), connectionID)
if err != nil { if err != nil {
errLog := logger.WithError(err) errLog := config.Logger.WithError(err)
switch err.(type) { switch err.(type) {
case dialError: case dialError:
errLog.Error("Unable to dial edge") errLog.Error("Unable to dial edge")
@ -214,7 +214,7 @@ func ServeTunnel(
errGroup, serveCtx := errgroup.WithContext(ctx) errGroup, serveCtx := errgroup.WithContext(ctx)
errGroup.Go(func() error { errGroup.Go(func() error {
err := RegisterTunnel(serveCtx, logger, handler.muxer, config, connectionID, originLocalIP) err := RegisterTunnel(serveCtx, handler.muxer, config, connectionID, originLocalIP)
if err == nil { if err == nil {
connectedFuse.Fuse(true) connectedFuse.Fuse(true)
backoff.SetGracePeriod() backoff.SetGracePeriod()
@ -226,9 +226,9 @@ func ServeTunnel(
updateMetricsTickC := time.Tick(config.MetricsUpdateFreq) updateMetricsTickC := time.Tick(config.MetricsUpdateFreq)
for { for {
select { select {
case <-serveCtx.Done(): case <-serveCtx.Done():
// UnregisterTunnel blocks until the RPC call returns // UnregisterTunnel blocks until the RPC call returns
err := UnregisterTunnel(logger, handler.muxer, config.GracePeriod) err := UnregisterTunnel(handler.muxer, config.GracePeriod, config.Logger)
handler.muxer.Shutdown() handler.muxer.Shutdown()
return err return err
case <-updateMetricsTickC: case <-updateMetricsTickC:
@ -241,7 +241,7 @@ func ServeTunnel(
// All routines should stop when muxer finish serving. When muxer is shutdown // All routines should stop when muxer finish serving. When muxer is shutdown
// gracefully, it doesn't return an error, so we need to return errMuxerShutdown // gracefully, it doesn't return an error, so we need to return errMuxerShutdown
// here to notify other routines to stop // here to notify other routines to stop
err := handler.muxer.Serve(serveCtx); err := handler.muxer.Serve(serveCtx)
if err == nil { if err == nil {
return muxerShutdownError{} return muxerShutdownError{}
} }
@ -285,8 +285,8 @@ func IsRPCStreamResponse(headers []h2mux.Header) bool {
return true return true
} }
func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxer, config *TunnelConfig, connectionID uint8, originLocalIP string) error { func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfig, connectionID uint8, originLocalIP string) error {
logger.Debug("initiating RPC stream to register") config.Logger.Debug("initiating RPC stream to register")
stream, err := muxer.OpenStream([]h2mux.Header{ stream, err := muxer.OpenStream([]h2mux.Header{
{Name: ":method", Value: "RPC"}, {Name: ":method", Value: "RPC"},
{Name: ":scheme", Value: "capnp"}, {Name: ":scheme", Value: "capnp"},
@ -301,8 +301,8 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe
return clientRegisterTunnelError{cause: err} return clientRegisterTunnelError{cause: err}
} }
conn := rpc.NewConn( conn := rpc.NewConn(
tunnelrpc.NewTransportLogger(logger.WithField("subsystem", "rpc-register"), rpc.StreamTransport(stream)), tunnelrpc.NewTransportLogger(config.Logger.WithField("subsystem", "rpc-register"), rpc.StreamTransport(stream)),
tunnelrpc.ConnLog(logger.WithField("subsystem", "rpc-transport")), tunnelrpc.ConnLog(config.Logger.WithField("subsystem", "rpc-transport")),
) )
defer conn.Close() defer conn.Close()
ts := tunnelpogs.TunnelServer_PogsClient{Client: conn.Bootstrap(ctx)} ts := tunnelpogs.TunnelServer_PogsClient{Client: conn.Bootstrap(ctx)}
@ -317,13 +317,13 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe
config.Hostname, config.Hostname,
config.RegistrationOptions(connectionID, originLocalIP), config.RegistrationOptions(connectionID, originLocalIP),
) )
LogServerInfo(logger, serverInfoPromise.Result(), connectionID, config.Metrics) LogServerInfo(serverInfoPromise.Result(), connectionID, config.Metrics, config.Logger)
if err != nil { if err != nil {
// RegisterTunnel RPC failure // RegisterTunnel RPC failure
return clientRegisterTunnelError{cause: err} return clientRegisterTunnelError{cause: err}
} }
for _, logLine := range registration.LogLines { for _, logLine := range registration.LogLines {
logger.Info(logLine) config.Logger.Info(logLine)
} }
if registration.Err == DuplicateConnectionError { if registration.Err == DuplicateConnectionError {
return dupConnRegisterTunnelError{} return dupConnRegisterTunnelError{}
@ -334,10 +334,12 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe
} }
} }
config.Logger.Info("Tunnel ID: " + registration.TunnelID)
return nil return nil
} }
func UnregisterTunnel(logger *logrus.Entry, muxer *h2mux.Muxer, gracePeriod time.Duration) error { func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration, logger *log.Logger) error {
logger.Debug("initiating RPC stream to unregister") logger.Debug("initiating RPC stream to unregister")
stream, err := muxer.OpenStream([]h2mux.Header{ stream, err := muxer.OpenStream([]h2mux.Header{
{Name: ":method", Value: "RPC"}, {Name: ":method", Value: "RPC"},
@ -363,10 +365,11 @@ func UnregisterTunnel(logger *logrus.Entry, muxer *h2mux.Muxer, gracePeriod time
return ts.UnregisterTunnel(ctx, gracePeriod.Nanoseconds()) return ts.UnregisterTunnel(ctx, gracePeriod.Nanoseconds())
} }
func LogServerInfo(logger *logrus.Entry, func LogServerInfo(
promise tunnelrpc.ServerInfo_Promise, promise tunnelrpc.ServerInfo_Promise,
connectionID uint8, connectionID uint8,
metrics *TunnelMetrics, metrics *TunnelMetrics,
logger *log.Logger,
) { ) {
serverInfoMessage, err := promise.Struct() serverInfoMessage, err := promise.Struct()
if err != nil { if err != nil {
@ -432,14 +435,13 @@ type TunnelHandler struct {
metrics *TunnelMetrics metrics *TunnelMetrics
// connectionID is only used by metrics, and prometheus requires labels to be string // connectionID is only used by metrics, and prometheus requires labels to be string
connectionID string connectionID string
logger *logrus.Entry logger *log.Logger
} }
var dialer = net.Dialer{DualStack: true} var dialer = net.Dialer{DualStack: true}
// NewTunnelHandler returns a TunnelHandler, origin LAN IP and error // NewTunnelHandler returns a TunnelHandler, origin LAN IP and error
func NewTunnelHandler(ctx context.Context, func NewTunnelHandler(ctx context.Context,
logger *logrus.Entry,
config *TunnelConfig, config *TunnelConfig,
addr string, addr string,
connectionID uint8, connectionID uint8,
@ -455,7 +457,7 @@ func NewTunnelHandler(ctx context.Context,
tags: config.Tags, tags: config.Tags,
metrics: config.Metrics, metrics: config.Metrics,
connectionID: uint8ToString(connectionID), connectionID: uint8ToString(connectionID),
logger: logger, logger: config.Logger,
} }
if h.httpClient == nil { if h.httpClient == nil {
h.httpClient = http.DefaultTransport h.httpClient = http.DefaultTransport
@ -484,7 +486,7 @@ func NewTunnelHandler(ctx context.Context,
IsClient: true, IsClient: true,
HeartbeatInterval: config.HeartbeatInterval, HeartbeatInterval: config.HeartbeatInterval,
MaxHeartbeats: config.MaxHeartbeats, MaxHeartbeats: config.MaxHeartbeats,
Logger: config.ProtocolLogger, Logger: config.ProtocolLogger.WithFields(log.Fields{}),
}) })
if err != nil { if err != nil {
return h, "", errors.New("TLS handshake error") return h, "", errors.New("TLS handshake error")
@ -510,7 +512,8 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
} }
h.AppendTagHeaders(req) h.AppendTagHeaders(req)
cfRay := FindCfRayHeader(req) cfRay := FindCfRayHeader(req)
h.logRequest(req, cfRay) lbProbe := isLBProbeRequest(req)
h.logRequest(req, cfRay, lbProbe)
if websocket.IsWebSocketUpgrade(req) { if websocket.IsWebSocketUpgrade(req) {
conn, response, err := websocket.ClientConnect(req, h.tlsConfig) conn, response, err := websocket.ClientConnect(req, h.tlsConfig)
if err != nil { if err != nil {
@ -522,7 +525,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
// connection because cloudflared doesn't operate on the message themselves // connection because cloudflared doesn't operate on the message themselves
websocket.Stream(conn.UnderlyingConn(), stream) websocket.Stream(conn.UnderlyingConn(), stream)
h.metrics.incrementResponses(h.connectionID, "200") h.metrics.incrementResponses(h.connectionID, "200")
h.logResponse(response, cfRay) h.logResponse(response, cfRay, lbProbe)
} }
} else { } else {
response, err := h.httpClient.RoundTrip(req) response, err := h.httpClient.RoundTrip(req)
@ -533,7 +536,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
stream.WriteHeaders(H1ResponseToH2Response(response)) stream.WriteHeaders(H1ResponseToH2Response(response))
io.Copy(stream, response.Body) io.Copy(stream, response.Body)
h.metrics.incrementResponses(h.connectionID, "200") h.metrics.incrementResponses(h.connectionID, "200")
h.logResponse(response, cfRay) h.logResponse(response, cfRay, lbProbe)
} }
} }
h.metrics.decrementConcurrentRequests(h.connectionID) h.metrics.decrementConcurrentRequests(h.connectionID)
@ -547,18 +550,22 @@ func (h *TunnelHandler) logError(stream *h2mux.MuxedStream, err error) {
h.metrics.incrementResponses(h.connectionID, "502") h.metrics.incrementResponses(h.connectionID, "502")
} }
func (h *TunnelHandler) logRequest(req *http.Request, cfRay string) { func (h *TunnelHandler) logRequest(req *http.Request, cfRay string, lbProbe bool) {
if cfRay != "" { if cfRay != "" {
h.logger.WithField("CF-RAY", cfRay).Infof("%s %s %s", req.Method, req.URL, req.Proto) h.logger.WithField("CF-RAY", cfRay).Infof("%s %s %s", req.Method, req.URL, req.Proto)
} else if lbProbe {
h.logger.Debugf("Load Balancer health check %s %s %s", req.Method, req.URL, req.Proto)
} else { } else {
h.logger.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto) h.logger.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto)
} }
h.logger.Debugf("Request Headers %+v", req.Header) h.logger.Debugf("Request Headers %+v", req.Header)
} }
func (h *TunnelHandler) logResponse(r *http.Response, cfRay string) { func (h *TunnelHandler) logResponse(r *http.Response, cfRay string, lbProbe bool) {
if cfRay != "" { if cfRay != "" {
h.logger.WithField("CF-RAY", cfRay).Infof("%s", r.Status) h.logger.WithField("CF-RAY", cfRay).Infof("%s", r.Status)
} else if lbProbe {
h.logger.Debugf("Response to Load Balancer health check %s", r.Status)
} else { } else {
h.logger.Infof("%s", r.Status) h.logger.Infof("%s", r.Status)
} }
@ -572,3 +579,7 @@ func (h *TunnelHandler) UpdateMetrics(connectionID string) {
func uint8ToString(input uint8) string { func uint8ToString(input uint8) string {
return strconv.FormatUint(uint64(input), 10) return strconv.FormatUint(uint64(input), 10)
} }
func isLBProbeRequest(req *http.Request) bool {
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
}

View File

@ -2,6 +2,7 @@ package pogs
import ( import (
"github.com/cloudflare/cloudflared/tunnelrpc" "github.com/cloudflare/cloudflared/tunnelrpc"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
"zombiezen.com/go/capnproto2" "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs" "zombiezen.com/go/capnproto2/pogs"
@ -30,6 +31,7 @@ type TunnelRegistration struct {
Url string Url string
LogLines []string LogLines []string
PermanentFailure bool PermanentFailure bool
TunnelID string `capnp:"tunnelID"`
} }
func MarshalTunnelRegistration(s tunnelrpc.TunnelRegistration, p *TunnelRegistration) error { func MarshalTunnelRegistration(s tunnelrpc.TunnelRegistration, p *TunnelRegistration) error {
@ -124,6 +126,7 @@ func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerT
if err != nil { if err != nil {
return err return err
} }
log.Info(registration.TunnelID)
return MarshalTunnelRegistration(result, registration) return MarshalTunnelRegistration(result, registration)
} }

View File

@ -17,6 +17,8 @@ struct TunnelRegistration {
logLines @2 :List(Text); logLines @2 :List(Text);
# In case of error, whether the client should attempt to reconnect. # In case of error, whether the client should attempt to reconnect.
permanentFailure @3 :Bool; permanentFailure @3 :Bool;
# Displayed to user
tunnelID @4 :Text;
} }
struct RegistrationOptions { struct RegistrationOptions {

View File

@ -119,12 +119,12 @@ type TunnelRegistration struct{ capnp.Struct }
const TunnelRegistration_TypeID = 0xf41a0f001ad49e46 const TunnelRegistration_TypeID = 0xf41a0f001ad49e46
func NewTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) { func NewTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}) st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4})
return TunnelRegistration{st}, err return TunnelRegistration{st}, err
} }
func NewRootTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) { func NewRootTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}) st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4})
return TunnelRegistration{st}, err return TunnelRegistration{st}, err
} }
@ -209,12 +209,31 @@ func (s TunnelRegistration) SetPermanentFailure(v bool) {
s.Struct.SetBit(0, v) s.Struct.SetBit(0, v)
} }
func (s TunnelRegistration) TunnelID() (string, error) {
p, err := s.Struct.Ptr(3)
return p.Text(), err
}
func (s TunnelRegistration) HasTunnelID() bool {
p, err := s.Struct.Ptr(3)
return p.IsValid() || err != nil
}
func (s TunnelRegistration) TunnelIDBytes() ([]byte, error) {
p, err := s.Struct.Ptr(3)
return p.TextBytes(), err
}
func (s TunnelRegistration) SetTunnelID(v string) error {
return s.Struct.SetText(3, v)
}
// TunnelRegistration_List is a list of TunnelRegistration. // TunnelRegistration_List is a list of TunnelRegistration.
type TunnelRegistration_List struct{ capnp.List } type TunnelRegistration_List struct{ capnp.List }
// NewTunnelRegistration creates a new list of TunnelRegistration. // NewTunnelRegistration creates a new list of TunnelRegistration.
func NewTunnelRegistration_List(s *capnp.Segment, sz int32) (TunnelRegistration_List, error) { func NewTunnelRegistration_List(s *capnp.Segment, sz int32) (TunnelRegistration_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}, sz) l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4}, sz)
return TunnelRegistration_List{l}, err return TunnelRegistration_List{l}, err
} }
@ -1226,88 +1245,89 @@ func (p TunnelServer_unregisterTunnel_Results_Promise) Struct() (TunnelServer_un
return TunnelServer_unregisterTunnel_Results{s}, err return TunnelServer_unregisterTunnel_Results{s}, err
} }
const schema_db8274f9144abc7e = "x\xda\x9cU_l\x14U\x17\xff\x9d{w;\xdb\xa4" + const schema_db8274f9144abc7e = "x\xda\x9cU_\x88Te\x14?\xbf\xef\x9b\x99;\x0b" +
"e;\x99m\x02\x1bH\x13\xd2\x86B\xbe\xf2\xc1\xc7\x87" + "\xbb\xce^\xee,\xe8\xa0\x0c\xc8.\xae\xd2\x9af\x96n" +
"B\xfd\xd3?\x02\xba\xb5\x94\xbdl1\x08}`\xd8\xbd" + "\x7f\xf6O\xab5\xdb\xba\xce\xe7\xaca\xeaC\xd7\x99\xcf" +
"l\xa7\xce\xcelffQH\x04%\x18#\x89\x04E" + "\xf1n3\xf7\x0e\xf7\xde1\x95\xd4\x12\xa5\x12\x92\xca|" +
"^0\x9a@\xe2\x8b\x0fj\xe2\x83\x89\xc1\x04_\xe4\x81" + "1\x0c\x15\x82\x8a\xa8\xa0 \x08\x03{\xc9\x07\x1f|\xa9" +
"\x07^\xd4h$1J\x88\x91h\x88\x8d\x9ahb\xc6" + "(\x12\xa2\xc4\x87\xa4\x90\x96z(\x88\x1b\xdf\x9d\xbd3" +
"\xdc\xd9\xce\xecR\x14\xd0\xb7\x99\xdf=\xf7\x9c\xdf9\xf7" + "\xe3Zj\xbd\xdd\xfb\xfb\xcew\xce\xef\x9c\xef\x9c\xdfY" +
"\x9c\xdfY\xf7+\x1be\xeb\xd3_\xa7\x01\xb1%\xdd\x11" + "\x91\xe0\xc3le\xfc\xfb8\x91\x18\x8b'\x82\x07\xab\x97" +
">X\xbbr\xfe\xbe3\x97\x8fC\xcf\xb3\xf0\xc8\x85\x89" + "\xce\xdew\xe2\xe2a\xd23,8pn<\xfd\x87\x7f" +
"\xdco\xc1\xb1\xaf\x00\xda\xf0\x19;L\xc6\xf7L\x03\x8c" + "\xe8;\"\xac\xfa\x8a\xed\x83\xf1\x13\xd3\x88\x8c\xabl#" +
"\xebl\x07(\xbc\xf2\x9f\x0b\x1f\x9ez\xff\xc5\xd7!V" + "!\xb8t\xd7\xb9O_\xf9\xe8\x857H,\x01\x88b" +
"\x11\x01)\x0d\xd8\xf0\x07\xfb\x9d@\x86\xceG@\xe1k" + "\x1a\xd1\xaa\xbf\xd8\x9f \x18:\x1f\"\x04\xaf\x7f\xf3\xd9" +
"_|4U{\xe5\xecy\xe8\xab\xe2\xf3\xcd\x9c1\xa4" + "d\xf5\xd5\x93gI_\x12\x9d\xaf\xe5\x8cQ,\xe8\xc9" +
"\xc2\xde\x02]\xbd\xb8>\xf5A\xf3$\xcd\xd5\xd1\x10\xbf" + "\xe1\xf2\xf9\x95\xb1O\x1a'q\xae\x8e\x06\xf85uu" +
"\xa1\xae\x8e\xf1wA\xe1\x8a\x1f\xc7\xbb\x9d\x9b\xc7.B" + "\x84\x7f@\x08\x16\xfd2\xdae_?t\x9e\xf4\x0cZ" +
"\xcfS\x8bE\xd3\xf0\x1b>A\xc6/\xea\xd3\xf8)2" + ",\x1a\x86?\xf0q\x18\xbf\xabO\xe3\xd7\xd0x|\xdb" +
"\x9e\xd8{\xfa\xd5\xf4\xf5\xd3\x97 \xf2\xd4n\xdd\xa1\xac" + "\xf1\xd7\xe2W\x8f_ \x91A\xbbuBY\xbf\x1cs" +
"_Nyd\x9c\x8b\x82\xbf\x91\xba\x9f\x81\xc2\xfc{\x0f" + "a\x9c\x09\x83\x9f\x8a\xdd\xcf\x08A\xe6\xc3\x07\xde\x1f-" +
"\xbc3^\xf9\xf2\xf2\"\xdfQf\x9d\xda\xbc\xd1\xab\xa9" + "}{q\x8e\xef0\xb3\x0em\xc6\xe8\xd1\xd4\x97\xae=" +
"/]{\x1a\x14\xb2\xeb\xe6\xb2\xe7>\x7f\xf8j[\x0a" + "C\x08\xd8Us\xc1s_?|\xb9-\x85\xaa\xf6#" +
"5\xed[B*\x9czb\xef\\\xe7\xb3\xd7\xae-\xa4" + "(\x16L>\xb1m\xbac\xff\x95+\xb3)@\x1d\x99" +
"@\xea\xc8\xd4\xa2\x14\x1a\x9a\xca~\xe3\xbe19\xb3i" + "Z\x98B]S\xd9\xaf~jDn_\xb3\xe5\x1a\xe9" +
"\xf7\x0d\xe8y~K!\xcfh\xc3d\xbc\x15\x059\xa7" + "\x19~C!Oh\x830\xde\x0a\x83\x9c\xd1.\x18K" +
"]2Vg4 <yd\xcb\x8e\xcd+?\x9eo" + "\x93\x1aQp\xec\xc0\xd8\xc6\xb5\x8b?\x9fiw\xa7'" +
"w\xa7g\xe6\x95\xbb\x81\x8crw`\xd3\x0f\x8f\x0e\x9c" + "g\x94\xbb\xbe\xa4r\xb7s\xcd\xcf\x8f\xf6\x1d\xfbbf" +
"\xfcd~\x11\xeb\xc8pkf\x0d\x19\xbb\x94\x1fC(" + "\x0e\xeb\xd0p]r\x19\x8c\xcd\xca\x8f!\x94\xf1\xf5\xf5" +
"\xe3\x9b\xdb\xde\xfc4\x9f\xcd\xff\xbc\xa8\x1eQ\xf5\x1a\x99" + "o~\x99Ie~\x9bS\x8f\x90~=9\x0d\xe3%" +
"92^\x8al_\xc8|\x87\xa10h8\x8e\xb4\xbd" + "e\xbb\xeaH2\x0b\x1a\x08\xfc\xbam\xcb\x8a[\x8b\x15" +
"z\xaa\xfc\xdf\xf8\xb3\xbc\xb6l\xd6\x9d\xfa\xf0\xd6g," + "\xef\x8e>\x8b\xcb\x8bf\xcd\xae\x0d\xae\xdbcy\xbee" +
"?\xb0\x9c\xeat\x84\x8f\x14]\xdb*\x1f*\x12\x89." + "\x97\xa7B|(\xefT\xac\xe2\xde< :\xc1\x88\xf4" +
"b\x80\xbeb\x18 \xd2{\xf7\x00\xc4t}\x1c\x18\xb1" + "E\x83D\x80\xde\xb3\x95\x08L\xd7G\x89\x86\xac\xb2\xed" +
"\xaa\x8e\xeb\xc9\xb0b\xf9e\xd7q$x98\xba\xdf" + "\xb82(Y^\xd1\xb1mI\xbc\xe8\x1f\xdcaVL" +
"\xb4M\xa7,\x93@\x1d\xb7\x07j\x06(I\xef\xa0\xf4" + "\xbb(\x9b\x81\x127\x07j\x04(Hw\xb7t\x97\xd7" +
"\xd66\x1cOV-?\x90^\x13\xee\x1f)\x9a\x9eY" + "mW\x96-\xcf\x97n\x03\xee\x1d\xca\x9b\xaeY\xf5D" +
"\xf3E\x8a\xa7\x80\x14\x01z\xf7Y@\xf4p\x12\xcb\x19" + "\x8c\xc7\x88b \xd2\xbbN\x12\x89n\x0e\xb1\x90!(" +
"\x85U\xcf,\xcb\xa2\xf4\xc8r+S\xa6\xe3\x96\xb8," + "\xbbfQ\xe6\xa5\x0b\xcb)M\x9a\xb6S\xe0\xb2\x888" +
"S\x1a\x8c\xd2\xa0$\xe8\x92\x7f\x1at\xa7\xf4\x1bv\xe0" + "1\xc4\x09\xcd\xa0\xf3\xfek\xd0M\xd2\xabW|\x8f\x9a" +
"#\xb9u\xe7\xfb\x8bn\x17\xcdlD\xb9+\xa1\xbcu" + "\xb7n}\x7f\xce\xed\xbc\x99\x0a)w6)\xaf\xdb\xaa" +
"\x8f\x1a0N\xa2\xc8H'\xca\xa9\x91\xd1\xb7O\x00b" + "&\x8cC\xe4\x19t \xadfF\xdf0N$&8" +
"\x92\x93\xd8\xcdHg,\x17\x95u\xd78 \x8a\x9c\xc4" + "\xc4\x16\x06\x9d\xb1tX\xd6\xcd\xa3D\"\xcf!\xb63" +
"\x0c\xa3\xd0\xf5\xac\xaa\xe5<\"\xc1\xbd\x80\xba\xc1\xa8\x1b" + "\x04\x8ek\x95-\xfb\x11I\xdc\xf5\xd1E\x0c]\x84`" +
"\x14\xce\xba~\xe0\x985\x09\x80\xba\xc0\xa8\x0bt\xd4\xad" + "\x97\xe3\xf9\xb6Y\x95D\x84Nb\xe8$\x1ctj\xbe" +
"\x07\x96\xeb\xf8\xd4\xd3\x1a\x07\x10\xf5\xb4\x95\xe0/\x1ex" + "\xe5\xd8\x1e\xba[\xf3@@w[\x09\xfe\xe1\x81G\xea" +
"\xac\x11\xccJ'\xb0\xca\xa6\xba\x0cDo\xdb\xa2\xbc\x12" + "\xfe.i\xfbV\xd1T\x97\x89\xc2\xb7mQ^L$" +
"\x10\xa3\x9c\xc4d\x1b\xe5\xc2\xff\xda\xf2\x88)o\xdf\xdf" + "\x869\xc4D\x1b\xe5\xdc=myD\x947\xech\xe5" +
"\xcaC{J\x1e\x8aY\xf5\xc9\x9ai\xd9\xf1_\x9c\xcc" + "\xa1=-\xf7F\xac\xb2\xb2jZ\x95\xe8/Jf\x84" +
"\x18\xb4\xc7[6w\xe2\xb73\xaa\xaa\x17\xb1\xdbQ\xef" + "\xb4\xc7[6\xb7\xe2\xb7)\xac\xaa\x1b\xb2\xdbX\xcb\x86" +
"\x8b2T\x1c\x07c\x8eF'M\x00\xa5\x0cq*\xe5" + "\x19*\x8e\xfd\x11G\xa3\x03\xe3D\x85$8\x0ai\xb4" +
"\xa8E\xd3\xd0i\x1c(u)|)\xb5\x98\x1a\xbd\x94" + "h\x1a:F\x89\x0a\x9d\x0a\x9f\x8f\x16S\xa3\x07\x19\xa2" +
"\x07J=\x0a_N\x8c\x88\xe7\x88\x03\xc62z\x1b(" + "B\xb7\xc2\x17\x82\x01<\x0dNd,\xc0\xbbD\x85\x85" +
"-W\xf0\xa02O\xf1\x1c\xa5\x00c r\xdf\xaf\xf0" + "\x0a\xeeW\xe61\x9eF\x8c\xc8\xe8\x0b\xdd\xf7*|\x85" +
"u\x0aO\xa7r\x94\x06\x8c!Z\x03\x94\x06\x15\xbeE" + "\xc2\xe3\xb14\xe2D\xc6\x00\x96\x11\x15\xfa\x15>\xa6\xf0" +
"\xe1\x1d,G\x1d\x801Fs@iT\xe1\x93\x0a\xd7" + "\x04K#Ad\x8c`\x9a\xa80\xac\xf0\x09\x85k\xf1" +
"\xd295\xa1F\x81<\xa0\xf4\x98\xc2\xa7\x15\x9eY\x9a" + "\xb4\x1aQ#\x07\x97\xa8\xf0\x98\xc2\xa7\x14\x9e\x9c\x9fF" +
"\xa3\x8c\x1a\xd7\x08/*|F\xe1\x9d\xcbr\xd4\x09\x18" + "R\xcdk\x88\xe7\x15\xbe]\xe1\x1d\x0b\xd2\xe8 2\x9e" +
"O\xd21\xa0\xb4[\xe1\x15b\x14\x96mK:A\xa1" + "\xc4!\xa2\xc2\x16\x85\x97\xc0\x10\x14+\x96\xb4\xfd\\\xa9" +
"\xd2\xfe\xe2\x07\xa5\xe7[\xae\x13\xffs\xd7OJ*\x17" + "\xfd\xc5wK\xd7\xb3\x1c;\xfa\xe7\x8e\xd7,\xa9\x9c\x1d" +
"\x06\x97\x9a\xedXt\xb3jr)\xdbRw\x10eA" + "\\4\xda1\xef\xa4\xd4\xe4\"\xd5\x92w\x02R\x84\xa0" +
"a\xddu\xed\xa9[;)\x1b\x98U\x9f\x96\x80\x8a\x9c" + "\xe68\x95\xc9\x1b;)\xe5\x9be\x0f\xf3\x08y\x0et" +
"\xa8\xa7\xa5\x96 \x05\x86\xd1\\\x97\x03\x0bY\xd7)T" + "\xb7\xe4\x92\xa0\xc0 \x9c\xeb\xa2oQ\xca\xb1s%$" +
"\xa8\x03\x8c:\x92\xa7\x9dt\xd1W6\xedB=ab" + "\x88!\xd1|\xda\x09\x87\xb2E\xb3\x92\xab5\x99X\xde" +
"\xf9c\x8d\xc0m\xd4\xd1W1\x03Y!\x02#\x02\x85" + "H\xddw\xea5\xca\x96L_\x96\x00b\x00!p\xeb" +
"^\xc3\xd9\xe6\xb9\xb5i\x92^\xcdrL\x1b\xc9I\xdc" + "\xf6z\xd7\xa9NA\xbaU\xcb6+\xd4<\x89z\x80" +
"\x03lq;\xf4\xd5\x87\xa7\xcd\xaaz\xfeL\xd2\xa2\xab" + "\xcdm\x87lmp\xca,\xab\xe7O6[t\xe92" +
"\xd7\x00\xa2\x9f\x93X\xd7\xd6\xa2C\xaaE\x079\x89\xff" + "\"\xd1\xcb!V\xb4\xb5\xe8\x80j\xd1~\x0eq/C" +
"3\xca\xaa9I\xda\xf1\xa0i7\xe4m\x8dw7A" + "J\xcdI\xb3\x1dw\x9b\x95\xba\xbc\xa9\xf1n'He" +
"\xaa\xca\xa0\xf9Up\x0e\xb8\xfdE\xd3\xd3\xcc\x9a\xff/" + "\xe97\xber\xf6N\xa77o\xba\x9aY\xf5\xfe\xe7\xed" +
"o\xef\x94~V\xe9J\xbb\x98\x0d\x03\"\xc3I\xe4\x18" + "M\xd2K)]i\x17\xb3A\"\x91\xe4\x10i\x86!" +
"\x8dx\x91\xecPOK\xef\x17M1\xff\xbbp#\xcd" + "7\x94\x1dt\xb7\x04\x7f\xce\x14\xf3\x7f\x0b7\xd4\x88\xd2" +
"(\xcd\x11N\x03\xc9\x92\xa5x\xb7\xe8\xe20\x98^\xd0" + "\x18\xe18Qs\xcb\"Z.\xba\xd8GL\xcfih" +
"\xa8\xb5\xd7(^c\xfaC\x1e\x98\xbeQ#\x96\xecu" + "-6D{L\x7f\xc8%\xa6\xaf\xd6\xc0\x9a\x8b\x1d\xd1" +
"\x8a\xf7\xb7\xbe\xfa\x04\x98>\xa0\x85\xb1\xc8a\xa4\x19r" + "\x02\xd7\x97\x1e%\xa6\xf7iA$r4\xd4\x089\x8c" +
"\x94\xc28;\xf4E\xf9\x8dR\x18+)\xc5b\x08\x8c" + " \xca\x8e\xb2a~\xc3\x08\"%E$\x86D\xc3\xc8" +
"R\x91\xee\xbd\xdc\xb7\x09q\x9f\x7f/\x15\x8b\x97\xde\xdd" + "\xe3\xce\xcb}\x93\x10g\xbd;\xa9X\xb4\xf5n_\xaf" +
"\xeb\xd5\x8c\x93U|U\xb5\xda\xfc\xce\x01\xa2\x8b\x93X" + "F\x9c\x94\xe2\xab\xaa\xd5\xe6w\x9aHtr\x88\xf9\x0c" +
"\xca(\xb4\xdd\x05U\xccN\xb5\xb5\xd0\x9d\xd4\xaaI8" + "A\xc5\x99U\xc5\xd4d[\x0b\xddJ\xad\x1a\x84#\xcd" +
"\xd6\xac\xac\xba\xac\xfc\xf7$\xfeM%\xa83\x9c\xc4l" + "J\xa9\xcb\xca\x7f\xba\xe9\x7f\xbf\x12\xd4=\x1c\xe2p[" +
"[\xb7J\x05\xee\xe3$\xec6A\xb5\xd4b\x98\xe5$" + "\xb7>\xaf\xc0g9\xc4\x8bm\x82zD-\x86\xc3\x1c" +
"\x8e'\x1a\xa5?\x7f\x02\x10\xc79\x89S\x8c4\xe9y" + "\xe2tS\xa3\xf4SG\x89\xc4i\x0e\xf1^K\xa0\xf4" +
"1%\xad\xe1\xb5$\xd6v\xab\x93\x96#}5\xd0\x0b" + "w\x94\xe1\xdb\x1c\xe2c\x06M\xban\xc4S\xab\xbb-" +
"3\xac\x8e\xd4\xe4\xd6\xa5W3\x1d\xe9P\xb0\xcd\xb4\xec" + "\xdd\xad8\xe5\x09\xcb\x96\x9e\x9a\xf2\xd9\xc1VGj\x9c" +
"\x86'\x91\x0c\xdd\x9f\x01\x00\x00\xff\xffR3\x9d#" "k\xd2\xad\x9a\xb6\xb4\xe1\xaf7\xadJ\xdd\x954w\x12" +
"scm\xe2\xf0w\x00\x00\x00\xff\xffk\x8c\xa4["
func init() { func init() {
schemas.Register(schema_db8274f9144abc7e, schemas.Register(schema_db8274f9144abc7e,