├── .gitattributes ├── .gitignore ├── CONTRIBUTORS ├── README.md ├── client ├── client.go ├── client_test.go ├── conn │ ├── connection.go │ └── connection_test.go ├── loadbalancer │ ├── loadbalancer.go │ └── roundrobin │ │ ├── roundrobin.go │ │ └── roundrobin_test.go ├── pool.go ├── pool_test.go ├── serviceclient.go └── serviceclient_test.go ├── config ├── config.go ├── config_test.go ├── defaults.go └── uuid.go ├── criteria.go ├── criteria_test.go ├── daemon ├── client.go ├── messages.go └── pipe.go ├── documentation ├── SkyNetFavicon.png ├── SkyNetLogo.png ├── SkynetIconv1.png ├── SkynetIconv2.png └── protocol.md ├── handshake.go ├── log ├── log.go └── multiwriter.go ├── logmessages.go ├── messages.go ├── pools ├── resourcepool.go └── ring.go ├── requestinfo.go ├── rpc └── bsonrpc │ ├── bsoncoders.go │ ├── bsoncoders_test.go │ ├── client.go │ ├── rpc_test.go │ └── server.go ├── service ├── admin.go ├── logmessages.go ├── service.go ├── servicerpc.go └── servicerpc_test.go ├── serviceinfo.go ├── servicemanager.go ├── stats ├── hoststats.go └── reporter.go ├── test ├── connection.go ├── loadbalancer.go ├── pool.go ├── serviceclient.go └── servicemanager.go └── tools ├── conf └── skynet.conf └── upstart └── skydaemon.conf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Copy this into a git repo to avoid slow packing of binaries. 2 | *.jpg binary -delta 3 | *.png binary -delta 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | nbproject/* 3 | examples/service/service 4 | cmd/skydaemon/skydaemon 5 | cmd/sky/sky 6 | *~ 7 | *.swp 8 | *.out 9 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Brian Ketelsen bketelsen@gmail.com 2 | Christopher Dunn cdunn2001@gmail.com 3 | Paul Bellamy 4 | Erik St. Martin alakriti@gmail.com 5 | John Asmuth jasmuth@gmail.com 6 | Xing Xing mikespook@gmail.com 7 | Reid Morrison reidmo@gmail.com 8 | Steve Phillips elimisteve@gmail.com 9 | Leo Franchi lfranchi@kde.org 10 | Jake Tews jake@keyboardfu.com 11 | René Kistl rene@kistl.at 12 | Michael Wilson thamixmastermike@gmail.com 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://github.com/skynetservices/skynet/raw/master/documentation/SkyNetLogo.png) 2 | 3 | ##Introduction 4 | Skynet is a communication protocol for building massively distributed apps in Go. 5 | It is not constrained to Go, so it will lend itself nicely to polyglot environments. 6 | The first planned language addition is Ruby. 7 | 8 | Skynet is currently undergoing a large refactoring. What's represented here used to exist under the skynet2 repo, and leverages zookeeper. We are currently refactoring to use the new SkyDNS and HTTP/JSON. This readme as well as extensive documentation will be released when the refactoring is completed. 9 | 10 | 11 | ##Open Source - MIT Software License 12 | Copyright (c) 2013 Brian Ketelsen 13 | 14 | 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: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | 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. 19 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/client/conn" 7 | "github.com/skynetservices/skynet/client/loadbalancer" 8 | "github.com/skynetservices/skynet/client/loadbalancer/roundrobin" 9 | "github.com/skynetservices/skynet/config" 10 | "github.com/skynetservices/skynet/log" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | const ( 16 | DIAL_TIMEOUT = 500 * time.Millisecond 17 | ) 18 | 19 | func init() { 20 | go mux() 21 | } 22 | 23 | // TODO: implement a way to report/remove instances that fail a number of times 24 | var ( 25 | network = "tcp" 26 | knownNetworks = []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram", "unixpacket"} 27 | serviceClients = []ServiceClientProvider{} 28 | 29 | closeChan = make(chan bool, 1) 30 | instanceWatcher = make(chan skynet.InstanceNotification, 100) 31 | 32 | pool ConnectionPooler = NewPool() 33 | LoadBalancerFactory loadbalancer.Factory = roundrobin.New 34 | waiter sync.WaitGroup 35 | ) 36 | 37 | var ( 38 | UnknownNetworkError = errors.New("Unknown network") 39 | ) 40 | 41 | /* 42 | client.GetNetwork() returns the network used for client connections (default tcp) 43 | tcp, tcp4, tcp6, udp, udp4, udp6, ip, ip4, ip6, unix, unixgram, unixpacket 44 | */ 45 | func GetNetwork() string { 46 | return network 47 | } 48 | 49 | /* 50 | client.SetNetwork() sets the network used for client connections (default tcp) 51 | tcp, tcp4, tcp6, udp, udp4, udp6, ip, ip4, ip6, unix, unixgram, unixpacket 52 | */ 53 | func SetNetwork(network string) error { 54 | for _, n := range knownNetworks { 55 | if n == network { 56 | return nil 57 | } 58 | } 59 | 60 | return UnknownNetworkError 61 | } 62 | 63 | /* 64 | client.SetLoadBalancer() provide a custom load balancer to determine the order in which instances are sent requests 65 | */ 66 | func SetLoadBalancerFactory(factory loadbalancer.Factory) { 67 | LoadBalancerFactory = factory 68 | } 69 | 70 | /* 71 | client.GetServiceFromCriteria() Returns a client specific to the skynet.Criteria provided. 72 | Only instances that match this criteria will service the requests. 73 | 74 | The core reason to use this over GetService() is that the load balancer will use the order of the criteria items to determine which datacenter it should roll over to first etc. 75 | */ 76 | func GetServiceFromCriteria(c *skynet.Criteria) ServiceClientProvider { 77 | sc := NewServiceClient(c) 78 | 79 | // This should block, we dont want to return the ServiceClient before the client has fully registered it 80 | addServiceClient(sc) 81 | 82 | return sc 83 | } 84 | 85 | /* 86 | client.Close() Closes all ServiceClient's and releases their network resources 87 | */ 88 | func Close() { 89 | waiter.Add(1) 90 | 91 | closeChan <- true 92 | 93 | // Wait for all ServiceClient's to finish and close their connections 94 | waiter.Wait() 95 | } 96 | 97 | /* 98 | client.GetService() Returns a client specific to the criteria provided 99 | Empty values will be treated as wildcards and will be determined to match everything 100 | */ 101 | func GetService(name string, version string, region string, host string) ServiceClientProvider { 102 | criteria := &skynet.Criteria{ 103 | Services: []skynet.ServiceCriteria{ 104 | skynet.ServiceCriteria{Name: name, Version: version}, 105 | }, 106 | } 107 | 108 | if region != "" { 109 | criteria.AddRegion(region) 110 | } 111 | 112 | if host != "" { 113 | criteria.AddHost(host) 114 | } 115 | 116 | s := GetServiceFromCriteria(criteria) 117 | 118 | return s 119 | } 120 | 121 | func mux() { 122 | for { 123 | select { 124 | case n := <-instanceWatcher: 125 | updateInstance(n) 126 | case <-closeChan: 127 | for _, sc := range serviceClients { 128 | sc.Close() 129 | } 130 | 131 | pool.Close() 132 | serviceClients = []ServiceClientProvider{} 133 | waiter.Done() 134 | } 135 | } 136 | } 137 | 138 | /* 139 | client.acquire will return an idle connection or a new one 140 | */ 141 | func acquire(s skynet.ServiceInfo) (c conn.Connection, err error) { 142 | return pool.Acquire(s) 143 | } 144 | 145 | /* 146 | client.release will release a resource for use by others. If the idle queue is 147 | full, the resource will be closed. 148 | */ 149 | func release(c conn.Connection) { 150 | pool.Release(c) 151 | } 152 | 153 | func addServiceClient(sc ServiceClientProvider) { 154 | serviceClients = append(serviceClients, sc) 155 | 156 | instances := skynet.GetServiceManager().Watch(sc, instanceWatcher) 157 | 158 | for _, i := range instances { 159 | pool.AddInstance(i) 160 | sc.Notify(skynet.InstanceNotification{Type: skynet.InstanceAdded, Service: i}) 161 | } 162 | } 163 | 164 | // TODO: we need a method here to removeServiceClient 165 | // it should remove instances from the pool if there are no remaining 166 | // ServiceClients that match the instance, and should end the watch from ServiceManager 167 | 168 | // only call from mux() 169 | func updateInstance(n skynet.InstanceNotification) { 170 | // Forward notification on to ServiceClients that match 171 | for _, sc := range serviceClients { 172 | if sc.Matches(n.Service) { 173 | go sc.Notify(n) 174 | } 175 | } 176 | 177 | // Update our internal pools 178 | switch n.Type { 179 | case skynet.InstanceAdded: 180 | go pool.AddInstance(n.Service) 181 | case skynet.InstanceUpdated: 182 | go pool.UpdateInstance(n.Service) 183 | case skynet.InstanceRemoved: 184 | go pool.RemoveInstance(n.Service) 185 | } 186 | 187 | } 188 | 189 | func getIdleConnectionsToInstance(s skynet.ServiceInfo) int { 190 | if n, err := config.Int(s.Name, s.Version, "client.conn.idle"); err == nil { 191 | return n 192 | } 193 | 194 | return config.DefaultIdleConnectionsToInstance 195 | } 196 | 197 | func getMaxConnectionsToInstance(s skynet.ServiceInfo) int { 198 | if n, err := config.Int(s.Name, s.Version, "client.conn.max"); err == nil { 199 | return n 200 | } 201 | 202 | return config.DefaultMaxConnectionsToInstance 203 | } 204 | 205 | func getIdleTimeout(s skynet.ServiceInfo) time.Duration { 206 | if d, err := config.String(s.Name, s.Version, "client.timeout.idle"); err == nil { 207 | if timeout, err := time.ParseDuration(d); err == nil { 208 | return timeout 209 | } 210 | 211 | log.Println(log.ERROR, "Failed to parse client.timeout.idle", err) 212 | } 213 | 214 | return config.DefaultIdleTimeout 215 | } 216 | -------------------------------------------------------------------------------- /client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/client/loadbalancer/roundrobin" 7 | "github.com/skynetservices/skynet/test" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // TODO: Validate MaxConnectionsToInstance and MaxIdleConnectionsToInstance 13 | var serviceManager = new(test.ServiceManager) 14 | 15 | func init() { 16 | skynet.SetServiceManager(serviceManager) 17 | } 18 | 19 | func TestGetNetwork(t *testing.T) { 20 | // Default to tcp 21 | if GetNetwork() != "tcp" { 22 | t.Fatal("GetNetwork() returned incorrect value") 23 | } 24 | } 25 | 26 | func TestSetNetwork(t *testing.T) { 27 | for _, n := range knownNetworks { 28 | err := SetNetwork(n) 29 | 30 | if err != nil { 31 | t.Fatal("SetNetwork() incorrectly rejected known network") 32 | } 33 | } 34 | 35 | err := SetNetwork("foo") 36 | 37 | if err == nil { 38 | t.Fatal("SetNetwork() accepted invalid network") 39 | } 40 | } 41 | 42 | func TestGetServiceFromCriteria(t *testing.T) { 43 | c := &skynet.Criteria{ 44 | Services: []skynet.ServiceCriteria{ 45 | skynet.ServiceCriteria{Name: "TestService"}, 46 | }, 47 | } 48 | 49 | c.AddRegion("Tampa") 50 | service := GetServiceFromCriteria(c) 51 | defer resetClient() 52 | 53 | if service.(*ServiceClient).criteria != c { 54 | t.Fatal("GetServiceFromCriteria() failed to associate critera with ServiceClient") 55 | } 56 | } 57 | 58 | func TestClientClose(t *testing.T) { 59 | closeCalled := false 60 | 61 | sc := test.ServiceClient{} 62 | sc.CloseFunc = func() { 63 | closeCalled = true 64 | } 65 | 66 | addServiceClient(ServiceClientProvider(&sc)) 67 | defer resetClient() 68 | 69 | Close() 70 | 71 | if !closeCalled { 72 | t.Fatal("Close() is expected to call Close() on all ServiceClients") 73 | } 74 | } 75 | 76 | func TestInstanceNotificationForwardedToServiceClient(t *testing.T) { 77 | timeout := 5 * time.Millisecond 78 | 79 | watch := make(chan interface{}) 80 | receive := make(chan interface{}) 81 | 82 | // watching isn't started till we have at least one ServiceClient 83 | sc := test.ServiceClient{ 84 | MatchesFunc: func(s skynet.ServiceInfo) bool { 85 | return true 86 | }, 87 | NotifyFunc: func(s skynet.InstanceNotification) { 88 | watch <- true 89 | }, 90 | } 91 | 92 | addServiceClient(ServiceClientProvider(&sc)) 93 | defer resetClient() 94 | 95 | si := serviceInfo() 96 | 97 | // Add 98 | go receiveOrTimeout(watch, receive, timeout) 99 | go sendInstanceNotification(skynet.InstanceAdded, *si) 100 | 101 | v := <-receive 102 | if _, fail := v.(error); fail { 103 | t.Fatal("Notify() is expected to be called on all matching ServiceClients") 104 | } 105 | 106 | // Update 107 | si.Registered = false 108 | 109 | go receiveOrTimeout(watch, receive, timeout) 110 | go sendInstanceNotification(skynet.InstanceUpdated, *si) 111 | 112 | v = <-receive 113 | if _, fail := v.(error); fail { 114 | t.Fatal("Notify() is expected to be called on all matching ServiceClients") 115 | } 116 | 117 | // Remove 118 | go receiveOrTimeout(watch, receive, timeout) 119 | go sendInstanceNotification(skynet.InstanceRemoved, *si) 120 | 121 | v = <-receive 122 | if _, fail := v.(error); fail { 123 | t.Fatal("Notify() is expected to be called on all matching ServiceClients") 124 | } 125 | 126 | } 127 | 128 | func TestInstanceNotificationsUpdatePool(t *testing.T) { 129 | watch := make(chan interface{}) 130 | receive := make(chan interface{}) 131 | timeout := 5 * time.Millisecond 132 | 133 | // watching isn't started till we have at least one ServiceClient 134 | sc := test.ServiceClient{ 135 | MatchesFunc: func(s skynet.ServiceInfo) bool { 136 | return true 137 | }, 138 | } 139 | 140 | addServiceClient(ServiceClientProvider(&sc)) 141 | defer resetClient() 142 | 143 | si := serviceInfo() 144 | 145 | // Add 146 | pool = &test.Pool{ 147 | AddInstanceFunc: func(s skynet.ServiceInfo) { 148 | watch <- true 149 | }, 150 | } 151 | 152 | go receiveOrTimeout(watch, receive, timeout) 153 | go sendInstanceNotification(skynet.InstanceAdded, *si) 154 | 155 | v := <-receive 156 | if _, fail := v.(error); fail { 157 | t.Fatal("Failed to notify Pool of InstanceNotification") 158 | } 159 | 160 | // Update 161 | si.Registered = false 162 | pool = &test.Pool{ 163 | UpdateInstanceFunc: func(s skynet.ServiceInfo) { 164 | watch <- true 165 | }, 166 | } 167 | 168 | go receiveOrTimeout(watch, receive, timeout) 169 | go sendInstanceNotification(skynet.InstanceUpdated, *si) 170 | 171 | v = <-receive 172 | if _, fail := v.(error); fail { 173 | t.Fatal("Failed to notify Pool of InstanceNotification") 174 | } 175 | 176 | // Remove 177 | pool = &test.Pool{ 178 | RemoveInstanceFunc: func(s skynet.ServiceInfo) { 179 | watch <- true 180 | }, 181 | } 182 | 183 | go receiveOrTimeout(watch, receive, timeout) 184 | go sendInstanceNotification(skynet.InstanceRemoved, *si) 185 | 186 | v = <-receive 187 | if _, fail := v.(error); fail { 188 | t.Fatal("Failed to notify Pool of InstanceNotification") 189 | } 190 | } 191 | 192 | func serviceInfo() *skynet.ServiceInfo { 193 | si := skynet.NewServiceInfo("TestService", "1.0.0") 194 | si.Registered = true 195 | 196 | return si 197 | } 198 | 199 | func resetClient() { 200 | serviceClients = []ServiceClientProvider{} 201 | 202 | network = "tcp" 203 | knownNetworks = []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram", "unixpacket"} 204 | 205 | pool = NewPool() 206 | LoadBalancerFactory = roundrobin.New 207 | } 208 | 209 | func sendInstanceNotification(typ int, si skynet.ServiceInfo) { 210 | instanceWatcher <- skynet.InstanceNotification{Type: typ, Service: si} 211 | } 212 | 213 | func receiveOrTimeout(watchChan chan interface{}, respChan chan interface{}, d time.Duration) { 214 | timeout := time.After(d) 215 | 216 | select { 217 | case v := <-watchChan: 218 | respChan <- v 219 | return 220 | case <-timeout: 221 | respChan <- errors.New("timeout") 222 | return 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /client/conn/connection.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/kr/pretty" 7 | "github.com/skynetservices/skynet" 8 | "github.com/skynetservices/skynet/log" 9 | "github.com/skynetservices/skynet/rpc/bsonrpc" 10 | "labix.org/v2/mgo/bson" 11 | "net" 12 | "net/rpc" 13 | "reflect" 14 | "time" 15 | ) 16 | 17 | // TODO: Abstract out BSON logic into an interface that can be proviced for Encoding/Decoding data to a supplied interface 18 | // this would allow developers to swap out the RPC logic, maybe implement our own ClientCodec/ServerCodec that have an additional WriteHandshake/ReadHandshake methods on each of them. 19 | // bson for example we could create a custom type that is composed of our methods and the normal rpc codec 20 | var ( 21 | HandshakeFailed = errors.New("Handshake Failed") 22 | ServiceUnregistered = errors.New("Service is unregistered") 23 | ConnectionClosed = errors.New("Connection is closed") 24 | ) 25 | 26 | type serviceError struct { 27 | msg string 28 | } 29 | 30 | func (se serviceError) Error() string { 31 | return se.msg 32 | } 33 | 34 | /* 35 | Connection 36 | */ 37 | 38 | type Connection interface { 39 | SetIdleTimeout(timeout time.Duration) 40 | Addr() string 41 | 42 | Close() 43 | IsClosed() bool 44 | 45 | Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 46 | SendTimeout(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}, timeout time.Duration) (err error) 47 | } 48 | 49 | /* 50 | Conn 51 | Implementation of Connection 52 | */ 53 | type Conn struct { 54 | addr string 55 | conn net.Conn 56 | clientID string 57 | serviceName string 58 | rpcClient *rpc.Client 59 | rpcClientCodec *bsonrpc.ClientCodec 60 | closed bool 61 | 62 | idleTimeout time.Duration 63 | } 64 | 65 | /* 66 | client.NewConnection() Establishes new connection to skynet service specified by addr 67 | */ 68 | func NewConnection(serviceName, network, addr string, timeout time.Duration) (conn Connection, err error) { 69 | c, err := net.DialTimeout(network, addr, timeout) 70 | 71 | if err != nil { 72 | return 73 | } 74 | 75 | conn, err = NewConnectionFromNetConn(serviceName, c) 76 | 77 | return 78 | } 79 | 80 | /* 81 | client.NewConn() Establishes new connection to skynet service with existing net.Conn 82 | This is beneficial if you want to communicate over a pipe 83 | */ 84 | func NewConnectionFromNetConn(serviceName string, c net.Conn) (conn Connection, err error) { 85 | cn := &Conn{conn: c} 86 | cn.addr = c.RemoteAddr().String() 87 | cn.serviceName = serviceName 88 | 89 | cn.rpcClientCodec = bsonrpc.NewClientCodec(cn.conn) 90 | 91 | err = cn.performHandshake() 92 | 93 | return cn, err 94 | } 95 | 96 | /* 97 | Conn.Close() Close network connection 98 | */ 99 | func (c *Conn) Close() { 100 | c.closed = true 101 | c.rpcClient.Close() 102 | } 103 | 104 | /* 105 | Conn.SetIdleTimeout() amount of time that can pass between requests before connection is closed 106 | */ 107 | func (c *Conn) SetIdleTimeout(timeout time.Duration) { 108 | c.idleTimeout = timeout 109 | } 110 | 111 | /* 112 | Conn.IsClosed() Specifies if connection is closed 113 | */ 114 | func (c Conn) IsClosed() bool { 115 | return c.closed 116 | } 117 | 118 | /* 119 | Conn.Addr() Specifies the network address 120 | */ 121 | func (c Conn) Addr() string { 122 | return c.addr 123 | } 124 | 125 | /* 126 | Conn.Send() Sends RPC request to service 127 | */ 128 | func (c *Conn) Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 129 | return c.SendTimeout(ri, fn, in, out, 0) 130 | } 131 | 132 | /* 133 | Conn.SendTimeout() Acts like Send but takes a timeout 134 | */ 135 | func (c *Conn) SendTimeout(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}, timeout time.Duration) (err error) { 136 | if c.IsClosed() { 137 | return ConnectionClosed 138 | } 139 | 140 | sin := skynet.ServiceRPCInWrite{ 141 | RequestInfo: ri, 142 | Method: fn, 143 | ClientID: c.clientID, 144 | } 145 | 146 | var b []byte 147 | b, err = bson.Marshal(in) 148 | if err != nil { 149 | return serviceError{fmt.Sprintf("Error calling bson.Marshal: %v", err)} 150 | } 151 | 152 | sin.In = bson.Binary{ 153 | 0x00, 154 | b, 155 | } 156 | 157 | type Resp struct { 158 | Out skynet.ServiceRPCOutRead 159 | Err error 160 | } 161 | 162 | respChan := make(chan *Resp) 163 | 164 | go func() { 165 | log.Println(log.TRACE, fmt.Sprintf("Sending Method call %s with ClientID %s to: %s", sin.Method, sin.ClientID, c.addr)) 166 | r := &Resp{} 167 | 168 | r.Err = c.rpcClient.Call(c.serviceName+".Forward", sin, &r.Out) 169 | log.Println(log.TRACE, fmt.Sprintf("Method call %s with ClientID %s from: %s completed", sin.Method, sin.ClientID, c.addr)) 170 | 171 | respChan <- r 172 | }() 173 | 174 | var r *Resp 175 | 176 | if timeout == 0 { 177 | timeout = 15 * time.Minute 178 | } 179 | 180 | t := time.After(timeout) 181 | 182 | select { 183 | case r = <-respChan: 184 | if r.Err != nil { 185 | err = serviceError{r.Err.Error()} 186 | c.Close() 187 | return 188 | } 189 | case <-t: 190 | err = fmt.Errorf("Connection: timing out request after %s", timeout.String()) 191 | c.Close() 192 | return 193 | } 194 | 195 | if r.Out.ErrString != "" { 196 | err = serviceError{r.Out.ErrString} 197 | return 198 | } 199 | 200 | err = bson.Unmarshal(r.Out.Out, out) 201 | if err != nil { 202 | log.Println(log.ERROR, "Error unmarshalling nested document") 203 | err = serviceError{err.Error()} 204 | c.Close() 205 | } 206 | 207 | log.Println(log.TRACE, pretty.Sprintf("Method call %s with ClientID %s from: %s returned: %s %+v", sin.Method, sin.ClientID, c.addr, reflect.TypeOf(out), out)) 208 | 209 | return 210 | } 211 | 212 | /* 213 | Conn.performHandshake Responsible for performing handshake with service 214 | */ 215 | func (c *Conn) performHandshake() (err error) { 216 | var sh skynet.ServiceHandshake 217 | log.Println(log.TRACE, "Reading ServiceHandshake") 218 | 219 | err = c.rpcClientCodec.Decoder.Decode(&sh) 220 | if err != nil { 221 | log.Println(log.ERROR, "Failed to decode ServiceHandshake", err) 222 | c.Close() 223 | 224 | return HandshakeFailed 225 | } 226 | 227 | c.clientID = sh.ClientID 228 | 229 | if sh.Name != c.serviceName { 230 | log.Println(log.ERROR, "Attempted to send request to incorrect service: "+sh.Name) 231 | c.Close() 232 | return HandshakeFailed 233 | } 234 | 235 | ch := skynet.ClientHandshake{ 236 | ClientID: c.clientID, 237 | } 238 | 239 | log.Println(log.TRACE, "Writing ClientHandshake") 240 | err = c.rpcClientCodec.Encoder.Encode(ch) 241 | if err != nil { 242 | log.Println(log.ERROR, "Failed to encode ClientHandshake", err) 243 | c.Close() 244 | 245 | return HandshakeFailed 246 | } 247 | 248 | if !sh.Registered { 249 | log.Println(log.ERROR, "Attempted to send request to unregistered service") 250 | return ServiceUnregistered 251 | } 252 | 253 | log.Println(log.TRACE, "Handing connection RPC layer") 254 | 255 | c.rpcClient = rpc.NewClientWithCodec(c.rpcClientCodec) 256 | 257 | return 258 | } 259 | -------------------------------------------------------------------------------- /client/conn/connection_test.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/rpc/bsonrpc" 7 | "labix.org/v2/mgo/bson" 8 | "net" 9 | "net/rpc" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | // TODO: One of these tests is bailing early and occassionaly causing EOF issues with BSON 15 | func TestHandshake(t *testing.T) { 16 | client, server := net.Pipe() 17 | 18 | go doServiceHandshake(server, true, t) 19 | 20 | cn, err := NewConnectionFromNetConn("TestService", client) 21 | c := cn.(*Conn) 22 | 23 | if err != nil { 24 | t.Fatal("Failed to perform handshake", err) 25 | } 26 | 27 | if c.rpcClient == nil { 28 | t.Fatal("rpc.Client not initialized") 29 | } 30 | 31 | c.Close() 32 | server.Close() 33 | } 34 | 35 | func TestErrorOnUnregistered(t *testing.T) { 36 | client, server := net.Pipe() 37 | defer client.Close() 38 | defer server.Close() 39 | 40 | go doServiceHandshake(server, false, t) 41 | 42 | _, err := NewConnectionFromNetConn("TestService", client) 43 | 44 | if err != ServiceUnregistered { 45 | t.Fatal("Connection should return error when service is unregistered") 46 | } 47 | } 48 | 49 | func TestDialConn(t *testing.T) { 50 | ln, err := net.Listen("tcp", ":51900") 51 | defer ln.Close() 52 | 53 | if err != nil { 54 | t.Error("Failed to bind to port for test") 55 | } 56 | 57 | go func() { 58 | conn, err := ln.Accept() 59 | if err == nil { 60 | go doServiceHandshake(conn, true, t) 61 | } 62 | }() 63 | 64 | c, err := NewConnection("TestService", "tcp", ":51900", 500*time.Millisecond) 65 | 66 | if err != nil || c == nil { 67 | t.Fatal("NewConnection() failed to establish tcp connection", err) 68 | } 69 | } 70 | 71 | func TestSetIdleTimeout(t *testing.T) { 72 | client, _ := net.Pipe() 73 | defer client.Close() 74 | 75 | c := Conn{conn: client} 76 | c.SetIdleTimeout(1 * time.Minute) 77 | 78 | if c.idleTimeout != 1*time.Minute { 79 | t.Fatal("IdleTimeout not set as expected") 80 | } 81 | } 82 | 83 | func TestSendTimeout(t *testing.T) { 84 | ln, err := net.Listen("tcp", ":51900") 85 | defer ln.Close() 86 | 87 | if err != nil { 88 | t.Error("Failed to bind to port for test") 89 | } 90 | 91 | go func() { 92 | conn, err := ln.Accept() 93 | if err == nil { 94 | doServiceHandshake(conn, true, t) 95 | time.Sleep(10 * time.Millisecond) 96 | } 97 | }() 98 | 99 | c, err := NewConnection("TestService", "tcp", ":51900", 500*time.Millisecond) 100 | 101 | if err != nil { 102 | t.Error("Failed to establish connection for test", err) 103 | } 104 | 105 | var o string 106 | err = c.SendTimeout(&skynet.RequestInfo{}, "foo", 10, &o, 2*time.Millisecond) 107 | 108 | if err == nil { 109 | t.Fatal("Expected SendTimeout to return timeout error") 110 | } 111 | } 112 | 113 | func TestSend(t *testing.T) { 114 | client, server := net.Pipe() 115 | go doServiceHandshake(server, true, t) 116 | 117 | cn, err := NewConnectionFromNetConn("TestRPCService", client) 118 | c := cn.(*Conn) 119 | 120 | s := rpc.NewServer() 121 | var ts TestRPCService 122 | s.Register(&ts) 123 | go s.ServeCodec(bsonrpc.NewServerCodec(server)) 124 | 125 | var tp TestParam 126 | tp.Val1 = "Hello World" 127 | tp.Val2 = 10 128 | 129 | ri := &skynet.RequestInfo{} 130 | 131 | ts.TestMethod = func(in skynet.ServiceRPCIn, out *skynet.ServiceRPCOut) (err error) { 132 | out.Out, err = bson.Marshal(&tp) 133 | 134 | var t TestParam 135 | 136 | if err != nil { 137 | return 138 | } 139 | 140 | if in.ClientID != c.clientID { 141 | return errors.New("Failed to set ClientID on request") 142 | } 143 | 144 | if in.Method != "Foo" { 145 | return errors.New("Failed to set Method on request") 146 | } 147 | 148 | if *in.RequestInfo != *ri { 149 | return errors.New("Failed to set RequestInfo on request") 150 | } 151 | 152 | err = bson.Unmarshal(in.In, &t) 153 | if err != nil { 154 | return 155 | } 156 | 157 | if t.Val1 != tp.Val1 || tp.Val2 != tp.Val2 { 158 | return errors.New("Request failed to send proper data") 159 | } 160 | 161 | return 162 | } 163 | 164 | err = c.Send(ri, "Foo", tp, &tp) 165 | if err != nil { 166 | t.Error(err) 167 | return 168 | } 169 | 170 | c.Close() 171 | server.Close() 172 | } 173 | 174 | func TestSendOnClosedConnection(t *testing.T) { 175 | client, server := net.Pipe() 176 | go doServiceHandshake(server, true, t) 177 | 178 | c, err := NewConnectionFromNetConn("TestService", client) 179 | c.Close() 180 | 181 | var tp TestParam 182 | tp.Val1 = "Hello World" 183 | tp.Val2 = 10 184 | 185 | ri := &skynet.RequestInfo{} 186 | 187 | err = c.Send(ri, "foo", tp.Val1, &tp.Val2) 188 | 189 | if err != ConnectionClosed { 190 | t.Fatal("Send() should not send when connection has been closed") 191 | } 192 | } 193 | 194 | /* 195 | * Test Helpers 196 | */ 197 | 198 | type TestParam struct { 199 | Val1 string 200 | Val2 int 201 | } 202 | 203 | type TestRPCService struct { 204 | TestMethod func(in skynet.ServiceRPCIn, out *skynet.ServiceRPCOut) (err error) 205 | } 206 | 207 | func (ts *TestRPCService) Forward(in skynet.ServiceRPCIn, out *skynet.ServiceRPCOut) (err error) { 208 | if ts.TestMethod != nil { 209 | return ts.TestMethod(in, out) 210 | } 211 | 212 | return errors.New("No Method Supplied") 213 | } 214 | 215 | func (ts TestRPCService) Foo(in TestParam, out *TestParam) (err error) { 216 | out.Val1 = in.Val1 + "world!" 217 | out.Val2 = in.Val2 + 5 218 | return 219 | } 220 | 221 | func doServiceHandshake(server net.Conn, registered bool, t *testing.T) { 222 | sh := skynet.ServiceHandshake{ 223 | Registered: registered, 224 | ClientID: "abc", 225 | } 226 | 227 | encoder := bsonrpc.NewEncoder(server) 228 | err := encoder.Encode(sh) 229 | if err != nil { 230 | t.Fatal("Failed to encode server handshake", err) 231 | } 232 | 233 | var ch skynet.ClientHandshake 234 | decoder := bsonrpc.NewDecoder(server) 235 | err = decoder.Decode(&ch) 236 | if err != nil { 237 | t.Fatal("Error calling bsonrpc.NewDecoder: ", err) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /client/loadbalancer/loadbalancer.go: -------------------------------------------------------------------------------- 1 | package loadbalancer 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | ) 7 | 8 | var ( 9 | NoInstances = errors.New("No instances") 10 | ) 11 | 12 | type LoadBalancer interface { 13 | AddInstance(s skynet.ServiceInfo) 14 | UpdateInstance(s skynet.ServiceInfo) 15 | RemoveInstance(s skynet.ServiceInfo) 16 | Choose() (skynet.ServiceInfo, error) 17 | } 18 | 19 | type Factory func(instances []skynet.ServiceInfo) LoadBalancer 20 | -------------------------------------------------------------------------------- /client/loadbalancer/roundrobin/roundrobin.go: -------------------------------------------------------------------------------- 1 | package roundrobin 2 | 3 | import ( 4 | "container/list" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/client/loadbalancer" 7 | "sync" 8 | ) 9 | 10 | type LoadBalancer struct { 11 | instances map[string]*list.Element 12 | instanceMutex sync.Mutex 13 | instanceList list.List 14 | current *list.Element 15 | } 16 | 17 | /* 18 | * New() returns a new RoundRobin LoadBalancer 19 | */ 20 | func New(instances []skynet.ServiceInfo) loadbalancer.LoadBalancer { 21 | lb := &LoadBalancer{ 22 | instances: make(map[string]*list.Element), 23 | } 24 | 25 | for _, i := range instances { 26 | lb.AddInstance(i) 27 | } 28 | 29 | return lb 30 | } 31 | 32 | func (lb *LoadBalancer) AddInstance(s skynet.ServiceInfo) { 33 | if _, ok := lb.instances[s.UUID]; ok { 34 | lb.UpdateInstance(s) 35 | return 36 | } 37 | 38 | lb.instanceMutex.Lock() 39 | defer lb.instanceMutex.Unlock() 40 | 41 | var e *list.Element 42 | 43 | if !s.Registered { 44 | e = &list.Element{Value: s} 45 | } else { 46 | e = lb.instanceList.PushBack(s) 47 | } 48 | 49 | lb.instances[s.UUID] = e 50 | } 51 | 52 | func (lb *LoadBalancer) UpdateInstance(s skynet.ServiceInfo) { 53 | if _, ok := lb.instances[s.UUID]; !ok { 54 | lb.AddInstance(s) 55 | return 56 | } 57 | 58 | lb.instanceMutex.Lock() 59 | defer lb.instanceMutex.Unlock() 60 | 61 | lb.instances[s.UUID].Value = s 62 | 63 | if s.Registered == false { 64 | lb.instanceList.Remove(lb.instances[s.UUID]) 65 | } 66 | } 67 | 68 | func (lb *LoadBalancer) RemoveInstance(s skynet.ServiceInfo) { 69 | lb.instanceMutex.Lock() 70 | defer lb.instanceMutex.Unlock() 71 | 72 | lb.instanceList.Remove(lb.instances[s.UUID]) 73 | delete(lb.instances, s.UUID) 74 | 75 | // current should be nil if we have no instances 76 | if lb.instanceList.Len() == 0 { 77 | lb.current = nil 78 | } 79 | } 80 | 81 | func (lb *LoadBalancer) Choose() (s skynet.ServiceInfo, err error) { 82 | if lb.current == nil { 83 | if lb.instanceList.Len() == 0 { 84 | return s, loadbalancer.NoInstances 85 | } 86 | 87 | lb.current = lb.instanceList.Front() 88 | return lb.current.Value.(skynet.ServiceInfo), nil 89 | } 90 | 91 | lb.current = lb.current.Next() 92 | 93 | if lb.current == nil { 94 | lb.current = lb.instanceList.Front() 95 | } 96 | 97 | s = lb.current.Value.(skynet.ServiceInfo) 98 | 99 | return s, nil 100 | } 101 | -------------------------------------------------------------------------------- /client/loadbalancer/roundrobin/roundrobin_test.go: -------------------------------------------------------------------------------- 1 | package roundrobin 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/client/loadbalancer" 6 | "testing" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | si := serviceInfo(true) 11 | si2 := serviceInfo(true) 12 | 13 | lb := New([]skynet.ServiceInfo{si, si2}).(*LoadBalancer) 14 | 15 | if len(lb.instances) != 2 { 16 | t.Fatal("Failed to update instances", len(lb.instances)) 17 | } 18 | 19 | if lb.instanceList.Len() != 2 { 20 | t.Fatal("Failed to update list", lb.instanceList.Len()) 21 | } 22 | } 23 | 24 | func TestAdd(t *testing.T) { 25 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 26 | 27 | si := serviceInfo(true) 28 | si2 := serviceInfo(true) 29 | si3 := serviceInfo(true) 30 | 31 | lb.AddInstance(si) 32 | lb.AddInstance(si2) 33 | lb.AddInstance(si3) 34 | 35 | if len(lb.instances) != 3 { 36 | t.Fatal("Failed to update instances") 37 | } 38 | 39 | if lb.instanceList.Len() != 3 { 40 | t.Fatal("Failed to update list") 41 | } 42 | 43 | // Ensure items are added in correct order 44 | if lb.instanceList.Front().Value.(skynet.ServiceInfo).UUID != si.UUID { 45 | t.Fatal("Failed to add instances in the correct order") 46 | } 47 | 48 | if lb.instanceList.Back().Value.(skynet.ServiceInfo).UUID != si3.UUID { 49 | t.Fatal("Failed to add instances in the correct order") 50 | } 51 | } 52 | 53 | func TestAddIgnoresDuplicates(t *testing.T) { 54 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 55 | 56 | si := serviceInfo(true) 57 | 58 | lb.AddInstance(si) 59 | lb.AddInstance(si) 60 | 61 | if len(lb.instances) != 1 { 62 | t.Fatal("Add did not ignore duplicates", len(lb.instances)) 63 | } 64 | 65 | if lb.instanceList.Len() != 1 { 66 | t.Fatal("Add did not ignore duplicates", lb.instanceList.Len()) 67 | } 68 | } 69 | 70 | func TestAddUpdatesDuplicate(t *testing.T) { 71 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 72 | 73 | si := serviceInfo(true) 74 | 75 | lb.AddInstance(si) 76 | si.Name = "Foo" 77 | lb.AddInstance(si) 78 | 79 | if len(lb.instances) != 1 { 80 | t.Fatal("Add did not ignore duplicates", len(lb.instances)) 81 | } 82 | 83 | if lb.instanceList.Len() != 1 { 84 | t.Fatal("Add did not ignore duplicates", lb.instanceList.Len()) 85 | } 86 | 87 | if lb.instances[si.UUID].Value.(skynet.ServiceInfo).Name != "Foo" { 88 | t.Fatal("Existing instance was not updated") 89 | } 90 | } 91 | 92 | func TestAddDoesNotAddUnregisteredToList(t *testing.T) { 93 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 94 | 95 | si := serviceInfo(true) 96 | si2 := serviceInfo(false) 97 | 98 | lb.AddInstance(si) 99 | lb.AddInstance(si2) 100 | 101 | if len(lb.instances) != 2 { 102 | t.Fatal("Failed to update instances", len(lb.instances)) 103 | } 104 | 105 | if lb.instanceList.Len() != 1 { 106 | t.Fatal("Unregistered instances should not be part of list", lb.instanceList.Len()) 107 | } 108 | } 109 | 110 | func TestUpdate(t *testing.T) { 111 | si := serviceInfo(true) 112 | si2 := serviceInfo(true) 113 | si3 := serviceInfo(true) 114 | 115 | lb := New([]skynet.ServiceInfo{si, si2, si3}).(*LoadBalancer) 116 | 117 | si2.Name = "Foo" 118 | lb.UpdateInstance(si2) 119 | 120 | if lb.instances[si2.UUID].Value.(skynet.ServiceInfo).Name != si2.Name { 121 | t.Fatal("Existing instance was not updated") 122 | } 123 | } 124 | func TestUpdateAddsInstanceIfItDoesntExist(t *testing.T) { 125 | si := serviceInfo(true) 126 | 127 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 128 | 129 | lb.UpdateInstance(si) 130 | 131 | if len(lb.instances) != 1 { 132 | t.Fatal("Failed to update instances") 133 | } 134 | 135 | if lb.instanceList.Len() != 1 { 136 | t.Fatal("Failed to update list") 137 | } 138 | } 139 | 140 | func TestUpdateRemovesUnregisteredFromList(t *testing.T) { 141 | si := serviceInfo(true) 142 | 143 | lb := New([]skynet.ServiceInfo{si}).(*LoadBalancer) 144 | 145 | si.Registered = false 146 | lb.UpdateInstance(si) 147 | 148 | if len(lb.instances) != 1 { 149 | t.Fatal("Unregistered instances should still be tracked") 150 | } 151 | 152 | if lb.instanceList.Len() != 0 { 153 | t.Fatal("Unregistered instances should not be in list") 154 | } 155 | } 156 | 157 | func TestRemove(t *testing.T) { 158 | si := serviceInfo(true) 159 | si2 := serviceInfo(true) 160 | si3 := serviceInfo(true) 161 | si4 := serviceInfo(true) 162 | 163 | lb := New([]skynet.ServiceInfo{si, si2, si3, si4}).(*LoadBalancer) 164 | 165 | lb.RemoveInstance(si4) 166 | 167 | if len(lb.instances) != 3 { 168 | t.Fatal("Failed to update instances") 169 | } 170 | 171 | if lb.instanceList.Len() != 3 { 172 | t.Fatal("Failed to update list") 173 | } 174 | 175 | // Ensure items are added in correct order 176 | if lb.instanceList.Front().Value.(skynet.ServiceInfo).UUID != si.UUID { 177 | t.Fatal("Failed to add instances in the correct order") 178 | } 179 | 180 | if lb.instanceList.Back().Value.(skynet.ServiceInfo).UUID != si3.UUID { 181 | t.Fatal("Failed to add instances in the correct order") 182 | } 183 | } 184 | 185 | func TestChooseReturnsErrorWhenEmpty(t *testing.T) { 186 | lb := New([]skynet.ServiceInfo{}).(*LoadBalancer) 187 | 188 | _, err := lb.Choose() 189 | 190 | if err != loadbalancer.NoInstances { 191 | t.Fatal("LoadBalancer should fail if no instances exist") 192 | } 193 | } 194 | 195 | func TestChoose(t *testing.T) { 196 | instances := []skynet.ServiceInfo{serviceInfo(true), serviceInfo(true), serviceInfo(true), serviceInfo(true)} 197 | 198 | lb := New(instances).(*LoadBalancer) 199 | 200 | // Check order 201 | for i := 0; i <= 3; i++ { 202 | s, err := lb.Choose() 203 | 204 | if err != nil || s.UUID != instances[i].UUID { 205 | t.Fatal("LoadBalancer did not properly iterate over instances") 206 | } 207 | } 208 | 209 | // Ensure Choose loops around when it hits the end 210 | s, err := lb.Choose() 211 | if err != nil || s.UUID != instances[0].UUID { 212 | t.Fatal("LoadBalancer did not properly iterate over instances") 213 | } 214 | } 215 | 216 | func serviceInfo(registered bool) skynet.ServiceInfo { 217 | si := skynet.NewServiceInfo(nil) 218 | si.Registered = registered 219 | 220 | return si 221 | } 222 | -------------------------------------------------------------------------------- /client/pool.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/client/conn" 7 | "github.com/skynetservices/skynet/pools" 8 | "sync" 9 | ) 10 | 11 | var UnknownService = errors.New("Service not known to connection pool") 12 | 13 | type ConnectionPooler interface { 14 | AddInstance(s skynet.ServiceInfo) 15 | UpdateInstance(s skynet.ServiceInfo) 16 | RemoveInstance(s skynet.ServiceInfo) 17 | 18 | Acquire(s skynet.ServiceInfo) (conn.Connection, error) 19 | Release(conn.Connection) 20 | 21 | Close() 22 | NumInstances() int 23 | NumConnections() int 24 | } 25 | 26 | /* 27 | client.Pool Manages connection pools to services 28 | */ 29 | type Pool struct { 30 | servicePools map[string]*servicePool 31 | addInstanceChan chan skynet.ServiceInfo 32 | updateInstanceChan chan skynet.ServiceInfo 33 | removeInstanceChan chan skynet.ServiceInfo 34 | closeChan chan bool 35 | closeWait sync.WaitGroup 36 | } 37 | 38 | /* 39 | client.NewPool returns a new connection pool 40 | */ 41 | func NewPool() *Pool { 42 | p := &Pool{ 43 | servicePools: make(map[string]*servicePool), 44 | addInstanceChan: make(chan skynet.ServiceInfo, 10), 45 | updateInstanceChan: make(chan skynet.ServiceInfo, 10), 46 | removeInstanceChan: make(chan skynet.ServiceInfo, 10), 47 | closeChan: make(chan bool), 48 | } 49 | 50 | go p.mux() 51 | 52 | return p 53 | } 54 | 55 | type servicePool struct { 56 | service skynet.ServiceInfo 57 | pool *pools.ResourcePool 58 | } 59 | 60 | func (sp *servicePool) Close() { 61 | sp.pool.Close() 62 | } 63 | 64 | func (sp *servicePool) NumResources() int { 65 | return sp.pool.NumResources() 66 | } 67 | 68 | func (p *Pool) mux() { 69 | for { 70 | select { 71 | case i := <-p.addInstanceChan: 72 | p.addInstanceMux(i) 73 | case i := <-p.removeInstanceChan: 74 | p.removeInstanceMux(i) 75 | case i := <-p.updateInstanceChan: 76 | p.updateInstanceMux(i) 77 | case <-p.closeChan: 78 | p.closeMux() 79 | return 80 | } 81 | } 82 | } 83 | 84 | /* 85 | Pool.AddInstance adds connections to instance to the pool 86 | */ 87 | func (p *Pool) AddInstance(s skynet.ServiceInfo) { 88 | go func() { 89 | p.addInstanceChan <- s 90 | }() 91 | } 92 | 93 | func (p *Pool) addInstanceMux(s skynet.ServiceInfo) { 94 | if _, ok := p.servicePools[s.AddrString()]; !ok { 95 | sp := &servicePool{ 96 | service: s, 97 | pool: pools.NewResourcePool(func() (pools.Resource, error) { 98 | c, err := conn.NewConnection(s.Name, GetNetwork(), s.AddrString(), DIAL_TIMEOUT) 99 | 100 | if err == nil { 101 | c.SetIdleTimeout(getIdleTimeout(s)) 102 | } 103 | 104 | return c, err 105 | }, 106 | getIdleConnectionsToInstance(s), 107 | getMaxConnectionsToInstance(s)), 108 | } 109 | 110 | p.servicePools[s.AddrString()] = sp 111 | } else { 112 | p.UpdateInstance(s) 113 | } 114 | } 115 | 116 | /* 117 | Pool.UpdateInstance updates information about instance, if it's unknown to the pool it will add it 118 | */ 119 | func (p *Pool) UpdateInstance(s skynet.ServiceInfo) { 120 | go func() { 121 | p.updateInstanceChan <- s 122 | }() 123 | } 124 | 125 | func (p *Pool) updateInstanceMux(s skynet.ServiceInfo) { 126 | if _, ok := p.servicePools[s.AddrString()]; !ok { 127 | p.AddInstance(s) 128 | return 129 | } 130 | 131 | p.servicePools[s.AddrString()].service = s 132 | } 133 | 134 | /* 135 | Pool.RemoveInstance removes this instance from the pool and closes all it's connections 136 | */ 137 | func (p *Pool) RemoveInstance(s skynet.ServiceInfo) { 138 | go func() { 139 | p.removeInstanceChan <- s 140 | }() 141 | } 142 | 143 | func (p *Pool) removeInstanceMux(s skynet.ServiceInfo) { 144 | delete(p.servicePools, s.AddrString()) 145 | } 146 | 147 | /* 148 | Pool.Acquire will return an idle connection or a new one 149 | */ 150 | func (p *Pool) Acquire(s skynet.ServiceInfo) (c conn.Connection, err error) { 151 | if _, ok := p.servicePools[s.AddrString()]; !ok { 152 | return nil, UnknownService 153 | } 154 | 155 | r, err := p.servicePools[s.AddrString()].pool.Acquire() 156 | 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | return r.(conn.Connection), nil 162 | } 163 | 164 | /* 165 | Pool.Release will release a resource for use by others. If the idle queue is 166 | full, the resource will be closed. 167 | */ 168 | func (p *Pool) Release(c conn.Connection) { 169 | if _, ok := p.servicePools[c.Addr()]; !ok { 170 | c.Close() 171 | return 172 | } 173 | 174 | p.servicePools[c.Addr()].pool.Release(c) 175 | } 176 | 177 | /* 178 | Pool.Close will close all network connections associated with all known services 179 | */ 180 | func (p *Pool) Close() { 181 | p.closeWait.Add(1) 182 | p.closeChan <- true 183 | 184 | p.closeWait.Wait() 185 | } 186 | 187 | func (p *Pool) closeMux() { 188 | for k, sp := range p.servicePools { 189 | sp.Close() 190 | delete(p.servicePools, k) 191 | } 192 | 193 | p.closeWait.Done() 194 | } 195 | 196 | /* 197 | Pool.NumConnections will return the total number of connections across all instances 198 | as many connections could be opening and closing this is an estimate 199 | */ 200 | func (p *Pool) NumConnections() (count int) { 201 | for _, sp := range p.servicePools { 202 | count += sp.NumResources() 203 | } 204 | 205 | return count 206 | } 207 | 208 | /* 209 | Pool.NumInstances will return the number of unique instances it's maintaining connections too 210 | */ 211 | func (p *Pool) NumInstances() int { 212 | return len(p.servicePools) 213 | } 214 | -------------------------------------------------------------------------------- /client/pool_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "testing" 6 | ) 7 | 8 | // TODO: need tests 9 | 10 | func TestPoolClose(t *testing.T) { 11 | si := skynet.NewServiceInfo("TestService", "1.0.0") 12 | si.Registered = true 13 | si.ServiceAddr.IPAddress = "127.0.0.1" 14 | si.ServiceAddr.Port = 9000 15 | 16 | p := NewPool() 17 | p.AddInstance(*si) 18 | 19 | p.Close() 20 | 21 | if len(p.servicePools) > 0 { 22 | t.Fatal("Close() did not close all service pools") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/serviceclient.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/skynetservices/skynet" 7 | "github.com/skynetservices/skynet/client/loadbalancer" 8 | "github.com/skynetservices/skynet/config" 9 | "github.com/skynetservices/skynet/log" 10 | "reflect" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | // TODO: Implement SendTimeout() 16 | // TODO: Implement SendOnceTimeout() 17 | 18 | var ( 19 | ServiceClientClosed = errors.New("Service client shutdown") 20 | RequestTimeout = errors.New("Request timed out") 21 | ) 22 | 23 | /* 24 | ServiceSender Responsible for sending requests to the cluster. 25 | This is mostly used as way to test that clients make appropriate requests to services without the need to run those services 26 | */ 27 | type ServiceClientProvider interface { 28 | SetDefaultTimeout(retry, giveup time.Duration) 29 | GetDefaultTimeout() (retry, giveup time.Duration) 30 | 31 | Close() 32 | 33 | Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 34 | SendOnce(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 35 | 36 | Notify(n skynet.InstanceNotification) 37 | Matches(n skynet.ServiceInfo) bool 38 | } 39 | 40 | type ServiceClient struct { 41 | loadBalancer loadbalancer.LoadBalancer 42 | criteria *skynet.Criteria 43 | shutdown bool 44 | closed bool 45 | 46 | retryTimeout time.Duration 47 | giveupTimeout time.Duration 48 | 49 | waiter sync.WaitGroup 50 | 51 | // mux channels 52 | muxChan chan interface{} 53 | instanceNotifications chan skynet.InstanceNotification 54 | timeoutChan chan timeoutLengths 55 | shutdownChan chan bool 56 | } 57 | 58 | /* 59 | client.NewServiceClient Initializes a new ClientService 60 | */ 61 | func NewServiceClient(c *skynet.Criteria) ServiceClientProvider { 62 | sc := &ServiceClient{ 63 | criteria: c, 64 | instanceNotifications: make(chan skynet.InstanceNotification, 100), 65 | timeoutChan: make(chan timeoutLengths), 66 | shutdownChan: make(chan bool), 67 | muxChan: make(chan interface{}), 68 | loadBalancer: LoadBalancerFactory([]skynet.ServiceInfo{}), 69 | 70 | retryTimeout: getRetryTimeout(c.Services[0].Name, c.Services[0].Version), 71 | giveupTimeout: getGiveupTimeout(c.Services[0].Name, c.Services[0].Version), 72 | } 73 | 74 | go sc.mux() 75 | 76 | return sc 77 | } 78 | 79 | /* 80 | ServiceClient.Send() will send a request to one of the available instances. In intervals of retry time, 81 | it will send additional requests to other known instances. If no response is heard after 82 | the giveup time has passed, it will return an error. 83 | */ 84 | func (c *ServiceClient) Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 85 | if c.closed { 86 | return ServiceClientClosed 87 | } 88 | 89 | retry, giveup := c.GetDefaultTimeout() 90 | return c.send(retry, giveup, ri, fn, in, out) 91 | } 92 | 93 | /* 94 | ServiceClient.SendOnce() will send a request to one of the available instances. If no response is heard after 95 | the giveup time has passed, it will return an error. 96 | */ 97 | func (c *ServiceClient) SendOnce(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 98 | if c.closed { 99 | return ServiceClientClosed 100 | } 101 | _, giveup := c.GetDefaultTimeout() 102 | return c.send(0, giveup, ri, fn, in, out) 103 | } 104 | 105 | /* 106 | ServiceClient.SetTimeout() sets the time before ServiceClient.Send() retries requests, and 107 | the time before ServiceClient.Send() and ServiceClient.SendOnce() give up. Setting retry 108 | or giveup to 0 indicates no retry or time out. 109 | */ 110 | func (c *ServiceClient) SetDefaultTimeout(retry, giveup time.Duration) { 111 | c.muxChan <- timeoutLengths{ 112 | retry: retry, 113 | giveup: giveup, 114 | } 115 | } 116 | 117 | /* 118 | ServiceClient.GetTimeout() returns current timeout values 119 | */ 120 | func (c *ServiceClient) GetDefaultTimeout() (retry, giveup time.Duration) { 121 | tls := <-c.timeoutChan 122 | retry, giveup = tls.retry, tls.giveup 123 | 124 | return 125 | } 126 | 127 | /* 128 | ServiceClient.Close() refuses any new requests, and waits for active requests to finish 129 | */ 130 | func (c *ServiceClient) Close() { 131 | c.shutdownChan <- true 132 | c.waiter.Wait() 133 | } 134 | 135 | /* 136 | ServiceClient.NewRequestInfo() create a new RequestInfo object specific to this service 137 | */ 138 | func (c *ServiceClient) NewRequestInfo() (ri *skynet.RequestInfo) { 139 | // TODO: Set 140 | ri = &skynet.RequestInfo{ 141 | RequestID: config.NewUUID(), 142 | } 143 | 144 | return 145 | } 146 | 147 | /* 148 | ServiceClient.Matches() determins if the provided Service matches the criteria associated with this client 149 | */ 150 | func (c *ServiceClient) Matches(s skynet.ServiceInfo) bool { 151 | return c.criteria.Matches(s) 152 | } 153 | 154 | /* 155 | ServiceClient.Notify() Update available instances based off provided InstanceNotification 156 | */ 157 | func (c *ServiceClient) Notify(n skynet.InstanceNotification) { 158 | c.instanceNotifications <- n 159 | } 160 | 161 | func (c *ServiceClient) send(retry, giveup time.Duration, ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 162 | if ri == nil { 163 | ri = c.NewRequestInfo() 164 | } 165 | 166 | attempts := make(chan sendAttempt) 167 | 168 | var retryTicker <-chan time.Time 169 | retryChan := make(chan bool, 1) 170 | if retry > 0 { 171 | retryTicker = time.Tick(retry) 172 | } 173 | 174 | var timeoutTimer <-chan time.Time 175 | if giveup > 0 { 176 | timeoutTimer = time.NewTimer(giveup).C 177 | } 178 | 179 | attemptCount := 1 180 | go c.attemptSend(retry, attempts, ri, fn, in, out) 181 | 182 | for { 183 | select { 184 | case <-retryTicker: 185 | retryChan <- true 186 | 187 | case <-retryChan: 188 | attemptCount++ 189 | ri.RetryCount++ 190 | log.Println(log.TRACE, fmt.Sprintf("Sending Attempt# %d with RequestInfo %+v", attemptCount, ri)) 191 | go c.attemptSend(retry, attempts, ri, fn, in, out) 192 | 193 | case <-timeoutTimer: 194 | err = RequestTimeout 195 | log.Println(log.WARN, fmt.Sprintf("Timing out request after %d attempts within %s ", attemptCount, giveup.String())) 196 | return 197 | 198 | case attempt := <-attempts: 199 | if attempt.err != nil { 200 | log.Println(log.ERROR, "Attempt Error: ", attempt.err) 201 | 202 | // If there is no retry timer we need to exit as retries were disabled 203 | if retryTicker == nil { 204 | return err 205 | } else { 206 | // Don't wait for next retry tick retry now 207 | retryChan <- true 208 | } 209 | 210 | continue 211 | } 212 | 213 | // copy into the caller's value 214 | v := reflect.Indirect(reflect.ValueOf(out)) 215 | v.Set(reflect.Indirect(reflect.ValueOf(attempt.result))) 216 | 217 | return 218 | } 219 | } 220 | } 221 | 222 | type sendAttempt struct { 223 | err error 224 | result interface{} 225 | } 226 | 227 | func (c *ServiceClient) attemptSend(timeout time.Duration, attempts chan sendAttempt, ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) { 228 | s, err := c.loadBalancer.Choose() 229 | 230 | if err != nil { 231 | attempts <- sendAttempt{err: err} 232 | return 233 | } 234 | 235 | conn, err := acquire(s) 236 | defer release(conn) 237 | 238 | if err != nil { 239 | attempts <- sendAttempt{err: err} 240 | return 241 | } 242 | 243 | // Create a new instance of the type, we dont want race conditions where 2 connections are unmarshalling to the same object 244 | res := sendAttempt{ 245 | result: reflect.New(reflect.Indirect(reflect.ValueOf(out)).Type()).Interface(), 246 | } 247 | 248 | err = conn.SendTimeout(ri, fn, in, res.result, timeout) 249 | 250 | if err != nil { 251 | res.err = err 252 | } 253 | 254 | attempts <- res 255 | } 256 | 257 | type timeoutLengths struct { 258 | retry, giveup time.Duration 259 | } 260 | 261 | func (c *ServiceClient) mux() { 262 | for { 263 | select { 264 | case mi := <-c.muxChan: 265 | switch m := mi.(type) { 266 | case timeoutLengths: 267 | c.retryTimeout = m.retry 268 | c.giveupTimeout = m.giveup 269 | } 270 | case n := <-c.instanceNotifications: 271 | c.handleInstanceNotification(n) 272 | 273 | case c.timeoutChan <- timeoutLengths{ 274 | retry: c.retryTimeout, 275 | giveup: c.giveupTimeout, 276 | }: 277 | 278 | case shutdown := <-c.shutdownChan: 279 | // TODO: Close out all channels, and this goroutine after waiting for requests to finish 280 | if shutdown { 281 | c.closed = true 282 | return 283 | } 284 | } 285 | } 286 | } 287 | 288 | // this should only be called by mux() 289 | func (c *ServiceClient) handleInstanceNotification(n skynet.InstanceNotification) { 290 | // TODO: ensure LoadBalancer is thread safe and call these as goroutines 291 | switch n.Type { 292 | case skynet.InstanceAdded: 293 | c.loadBalancer.AddInstance(n.Service) 294 | case skynet.InstanceUpdated: 295 | c.loadBalancer.UpdateInstance(n.Service) 296 | case skynet.InstanceRemoved: 297 | c.loadBalancer.RemoveInstance(n.Service) 298 | } 299 | } 300 | 301 | func getRetryTimeout(service, version string) time.Duration { 302 | if d, err := config.String(service, version, "client.timeout.retry"); err == nil { 303 | if timeout, err := time.ParseDuration(d); err == nil { 304 | log.Println(log.TRACE, fmt.Sprintf("Using custom retry duration %q for %q %q", timeout.String(), service, version)) 305 | return timeout 306 | } 307 | 308 | log.Println(log.ERROR, "Failed to parse client.timeout.total", err) 309 | } 310 | 311 | return config.DefaultRetryDuration 312 | } 313 | 314 | func getGiveupTimeout(service, version string) time.Duration { 315 | if d, err := config.String(service, version, "client.timeout.total"); err == nil { 316 | if timeout, err := time.ParseDuration(d); err == nil { 317 | log.Println(log.TRACE, fmt.Sprintf("Using custom giveup duration %q for %q %q", timeout.String(), service, version)) 318 | return timeout 319 | } 320 | 321 | log.Println(log.ERROR, "Failed to parse client.timeout.total", err) 322 | } 323 | 324 | return config.DefaultTimeoutDuration 325 | } 326 | -------------------------------------------------------------------------------- /client/serviceclient_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/client/conn" 6 | "github.com/skynetservices/skynet/test" 7 | "labix.org/v2/mgo/bson" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // TODO: Test Send() 13 | // TODO: Test SendOnce() 14 | // TODO: Test SendTimeout() 15 | // TODO: Test SendOnceTimeout() 16 | // TODO: Test Timeout/Retry logic 17 | // TODO: Test Close() closes connections 18 | // TODO: Test that NewServiceClient gets a LoadBalancer from the factory 19 | // TODO: Test error conditions from connection 20 | 21 | func TestSetTimeout(t *testing.T) { 22 | defer resetClient() 23 | 24 | s := GetService("foo", "1.0.0", "", "") 25 | 26 | s.SetDefaultTimeout(5*time.Second, 10*time.Second) 27 | 28 | retry, giveup := s.GetDefaultTimeout() 29 | 30 | if retry != 5*time.Second || giveup != 10*time.Second { 31 | t.Fatal("SetDefaultTimeout() timeout values not set correctly") 32 | } 33 | } 34 | 35 | func TestInstanceNotificationsUpdateLoadBalancer(t *testing.T) { 36 | defer resetClient() 37 | 38 | watch := make(chan interface{}) 39 | receive := make(chan interface{}) 40 | timeout := 5 * time.Millisecond 41 | 42 | // watching isn't started till we have at least one ServiceClient 43 | criteria := &skynet.Criteria{Services: []skynet.ServiceCriteria{ 44 | skynet.ServiceCriteria{ 45 | Name: "TestService", 46 | Version: "", 47 | }, 48 | }} 49 | sc := NewServiceClient(criteria) 50 | sClient := sc.(*ServiceClient) 51 | 52 | // Use stub pool, we dont want real connections being made 53 | pool = &test.Pool{} 54 | 55 | addServiceClient(sc) 56 | 57 | si := serviceInfo() 58 | si.UUID = "FOO" 59 | 60 | // Add 61 | sClient.loadBalancer = &test.LoadBalancer{ 62 | AddInstanceFunc: func(s skynet.ServiceInfo) { 63 | watch <- true 64 | }, 65 | } 66 | 67 | go receiveOrTimeout(watch, receive, timeout) 68 | go sendInstanceNotification(skynet.InstanceAdded, *si) 69 | 70 | v := <-receive 71 | if _, fail := v.(error); fail { 72 | t.Fatal("Failed to notify LoadBalancer of InstanceNotification") 73 | } 74 | 75 | // Update 76 | si.Registered = false 77 | sClient.loadBalancer = &test.LoadBalancer{ 78 | UpdateInstanceFunc: func(s skynet.ServiceInfo) { 79 | watch <- true 80 | }, 81 | } 82 | 83 | go receiveOrTimeout(watch, receive, timeout) 84 | go sendInstanceNotification(skynet.InstanceUpdated, *si) 85 | 86 | v = <-receive 87 | if _, fail := v.(error); fail { 88 | t.Fatal("Failed to notify LoadBalancer of InstanceNotification") 89 | } 90 | 91 | // Remove 92 | sClient.loadBalancer = &test.LoadBalancer{ 93 | RemoveInstanceFunc: func(s skynet.ServiceInfo) { 94 | watch <- true 95 | }, 96 | } 97 | 98 | go receiveOrTimeout(watch, receive, timeout) 99 | go sendInstanceNotification(skynet.InstanceRemoved, *si) 100 | 101 | v = <-receive 102 | if _, fail := v.(error); fail { 103 | t.Fatal("Failed to notify LoadBalancer of InstanceNotification") 104 | } 105 | } 106 | 107 | func TestCloseRefusesNewRequests(t *testing.T) { 108 | s := GetService("foo", "1.0.0", "", "") 109 | s.Close() 110 | 111 | var val string 112 | 113 | err := s.Send(nil, "Foo", val, &val) 114 | if err != ServiceClientClosed { 115 | t.Fatal("Close() did not refuse new requests") 116 | } 117 | 118 | err = s.SendOnce(nil, "Foo", val, &val) 119 | if err != ServiceClientClosed { 120 | t.Fatal("Close() did not refuse new requests") 121 | } 122 | } 123 | 124 | func TestSend(t *testing.T) { 125 | called := false 126 | 127 | type r struct { 128 | Bar string 129 | } 130 | 131 | request := 20 132 | response := r{""} 133 | 134 | sc := GetService("foo", "1.0.0", "", "") 135 | stubForSend(sc, func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 136 | called = true 137 | 138 | resp := r{ 139 | "Foo", 140 | } 141 | 142 | b, err := bson.Marshal(resp) 143 | 144 | if err != nil { 145 | t.Error(err) 146 | } 147 | 148 | err = bson.Unmarshal(b, out) 149 | 150 | return err 151 | }) 152 | 153 | err := sc.Send(nil, "bar", request, &response) 154 | 155 | if err != nil { 156 | t.Error(err) 157 | } 158 | 159 | if !called { 160 | t.Fatal("Send not called") 161 | } 162 | 163 | if response.Bar != "Foo" { 164 | t.Fatal("response value failed to copy") 165 | } 166 | } 167 | 168 | // Helper for validating and testing send logic 169 | // stubs ServiceManager, Pool, Connection, LoadBalancer 170 | func stubForSend(sc ServiceClientProvider, f func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error)) { 171 | sm := &test.ServiceManager{} 172 | skynet.SetServiceManager(skynet.ServiceManager(sm)) 173 | 174 | pool = &test.Pool{ 175 | AcquireFunc: func(s skynet.ServiceInfo) (conn.Connection, error) { 176 | c := &test.Connection{ 177 | SendTimeoutFunc: func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}, timeout time.Duration) (err error) { 178 | return f(ri, fn, in, out) 179 | }, 180 | } 181 | 182 | return c, nil 183 | }, 184 | } 185 | 186 | sClient := sc.(*ServiceClient) 187 | sClient.loadBalancer = &test.LoadBalancer{ 188 | ChooseFunc: func() (s skynet.ServiceInfo, err error) { 189 | return 190 | }, 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "github.com/robfig/config" 6 | "github.com/skynetservices/skynet/log" 7 | "os" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | var defaultConfigFiles = []string{ 13 | "./skynet.conf", 14 | "/etc/skynet/skynet.conf", 15 | } 16 | 17 | var configFile string 18 | var uuid string 19 | var conf *config.Config 20 | 21 | func init() { 22 | flagset := flag.NewFlagSet("config", flag.ContinueOnError) 23 | flagset.StringVar(&configFile, "config", "", "Config File") 24 | flagset.StringVar(&uuid, "uuid", "", "uuid") 25 | 26 | args, _ := SplitFlagsetFromArgs(flagset, os.Args[1:]) 27 | flagset.Parse(args) 28 | 29 | // Ensure we have a UUID 30 | if uuid == "" { 31 | uuid = NewUUID() 32 | } 33 | 34 | if configFile == "" { 35 | for _, f := range defaultConfigFiles { 36 | if _, err := os.Stat(f); err == nil { 37 | configFile = f 38 | break 39 | } 40 | } 41 | } 42 | 43 | if configFile == "" { 44 | log.Println(log.ERROR, "Failed to find config file") 45 | conf = config.NewDefault() 46 | return 47 | } 48 | 49 | if _, err := os.Stat(configFile); os.IsNotExist(err) { 50 | log.Println(log.ERROR, "Config file does not exist", err) 51 | conf = config.NewDefault() 52 | return 53 | } 54 | 55 | var err error 56 | if conf, err = config.ReadDefault(configFile); err != nil { 57 | conf = config.NewDefault() 58 | log.Fatal(err) 59 | } 60 | 61 | // Set default log level from config, this can be overriden at the service level when the service is created 62 | if l, err := conf.RawStringDefault("log.level"); err == nil { 63 | log.SetLogLevel(log.LevelFromString(l)) 64 | } 65 | 66 | // Set default log level from config, this can be overriden at the service level when the service is created 67 | if lh, err := conf.RawStringDefault("log.sysloghost"); err == nil { 68 | log.SetSyslogHost(lh) 69 | } 70 | 71 | // Set syslog port 72 | if i, err := conf.Int("DEFAULT", "log.syslogport"); err == nil { 73 | log.SetSyslogPort(i) 74 | } 75 | 76 | // Set GOMAXPROCS 77 | if i, err := conf.Int("DEFAULT", "runtime.gomaxprocs"); err == nil { 78 | runtime.GOMAXPROCS(i) 79 | } 80 | } 81 | 82 | func String(service, version, option string) (string, error) { 83 | s := getSection(service, version) 84 | 85 | return conf.String(s, option) 86 | } 87 | 88 | func Bool(service, version, option string) (bool, error) { 89 | s := getSection(service, version) 90 | 91 | return conf.Bool(s, option) 92 | } 93 | 94 | func Duration(service, version, option string) (d time.Duration, err error) { 95 | s, err := String(service, version, option) 96 | if err != nil { 97 | return 98 | } 99 | return time.ParseDuration(s) 100 | } 101 | 102 | func Int(service, version, option string) (int, error) { 103 | s := getSection(service, version) 104 | 105 | return conf.Int(s, option) 106 | } 107 | 108 | func RawString(service, version, option string) (string, error) { 109 | s := getSection(service, version) 110 | 111 | return conf.RawString(s, option) 112 | } 113 | 114 | func RawStringDefault(option string) (string, error) { 115 | return conf.RawStringDefault(option) 116 | } 117 | 118 | func getSection(service, version string) string { 119 | s := service + "-" + version 120 | if conf.HasSection(s) { 121 | return s 122 | } 123 | 124 | return service 125 | } 126 | 127 | func getFlagName(f string) (name string) { 128 | if f[0] == '-' { 129 | minusCount := 1 130 | 131 | if f[1] == '-' { 132 | minusCount++ 133 | } 134 | 135 | f = f[minusCount:] 136 | 137 | for i := 0; i < len(f); i++ { 138 | if f[i] == '=' || f[i] == ' ' { 139 | break 140 | } 141 | 142 | name += string(f[i]) 143 | } 144 | } 145 | 146 | return 147 | } 148 | 149 | func UUID() string { 150 | return uuid 151 | } 152 | 153 | func SplitFlagsetFromArgs(flagset *flag.FlagSet, args []string) (flagsetArgs []string, additionalArgs []string) { 154 | for _, f := range args { 155 | if flagset.Lookup(getFlagName(f)) != nil { 156 | flagsetArgs = append(flagsetArgs, f) 157 | } else { 158 | additionalArgs = append(additionalArgs, f) 159 | } 160 | } 161 | 162 | return 163 | } 164 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func clearEnv() { 10 | os.Setenv("SKYNET_BIND_IP", "") 11 | os.Setenv("SKYNET_MIN_PORT", "") 12 | os.Setenv("SKYNET_MAX_PORT", "") 13 | os.Setenv("SKYNET_REGION", "") 14 | os.Setenv("SKYNET_VERSION", "") 15 | } 16 | 17 | func TestSplitFlagsetFromArgs(t *testing.T) { 18 | var a, b, c, e, f, g string 19 | var d, i bool 20 | 21 | flagset1 := flag.NewFlagSet("TestFlagFilter1", flag.ExitOnError) 22 | flagset1.StringVar(&a, "a", "x", "a") 23 | flagset1.StringVar(&b, "b", "x", "b") 24 | flagset1.StringVar(&c, "c", "x", "c") 25 | flagset1.BoolVar(&d, "d", false, "d") 26 | 27 | flagset2 := flag.NewFlagSet("TestFlagFilter2", flag.ExitOnError) 28 | flagset2.StringVar(&e, "e", "x", "e") 29 | flagset2.StringVar(&f, "f", "x", "f") 30 | flagset2.StringVar(&g, "g", "x", "g") 31 | flagset2.BoolVar(&i, "i", false, "i") 32 | 33 | args := []string{ 34 | // in flagset 35 | "--a=a", 36 | "-b=b", 37 | "-c=c", 38 | "-d", 39 | // not in flagset 40 | "--e=e", 41 | "-f=f", 42 | "-g=g", 43 | "-i", 44 | } 45 | 46 | flagsetArgs, additionalArgs := SplitFlagsetFromArgs(flagset1, args) 47 | 48 | // Validate flags 49 | if len(flagsetArgs) != 4 { 50 | t.Error("didn't split flagset") 51 | } 52 | 53 | if flagsetArgs[0] != args[0] { 54 | t.Error("didn't properly split flagset") 55 | } 56 | 57 | if flagsetArgs[1] != args[1] { 58 | t.Error("didn't properly split flagset") 59 | } 60 | 61 | if flagsetArgs[2] != args[2] { 62 | t.Error("didn't properly split flagset") 63 | } 64 | 65 | if flagsetArgs[3] != args[3] { 66 | t.Error("didn't properly split flagset") 67 | } 68 | 69 | if flagset1.Parse(flagsetArgs) != nil { 70 | t.Error("didn't return proper flagset arguments, flags failed to parse") 71 | } 72 | 73 | if len(flagset1.Args()) > 0 { 74 | t.Error("unparsed flags returned from parse") 75 | } 76 | 77 | if len(additionalArgs) != 4 { 78 | t.Error("didn't return proper additional arguments") 79 | } 80 | 81 | if additionalArgs[0] != args[4] { 82 | t.Error("didn't return proper additional arguments") 83 | } 84 | 85 | if additionalArgs[1] != args[5] { 86 | t.Error("didn't return proper additional arguments") 87 | } 88 | if additionalArgs[2] != args[6] { 89 | t.Error("didn't return proper additional arguments") 90 | } 91 | if additionalArgs[3] != args[7] { 92 | t.Error("didn't return proper additional arguments") 93 | } 94 | 95 | if flagset2.Parse(additionalArgs) != nil { 96 | t.Error("didn't return proper additional arguments, flags failed to parse") 97 | } 98 | 99 | if len(flagset2.Args()) > 0 { 100 | t.Error("unparsed flags returned from parse") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /config/defaults.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/skynetservices/skynet/log" 6 | "time" 7 | ) 8 | 9 | // skynet/client 10 | const ( 11 | // DefaultRetryDuration is how long a client.ServiceClient waits before sending a new request. 12 | DefaultRetryDuration = 2 * time.Second 13 | // DefaultTimeoutDuration is how long a client.ServiceClient will wait before giving up. 14 | DefaultTimeoutDuration = 10 * time.Second 15 | // DefaultIdleConnectionsToInstance is the number of connections to a particular instance that may sit idle. 16 | DefaultIdleConnectionsToInstance = 2 17 | // DefaultMaxConnectionsToInstance is the maximum number of concurrent connections to a particular instance. 18 | DefaultMaxConnectionsToInstance = 20 19 | ) 20 | 21 | // skynet 22 | const ( 23 | DefaultIdleTimeout = 0 24 | DefaultRegion = "unknown" 25 | DefaultVersion = "unknown" 26 | DefaultHost = "127.0.0.1" 27 | DefaultMinPort = 9000 28 | DefaultMaxPort = 9999 29 | 30 | DefaultSyslogHost = "" 31 | DefaultSyslogPort = 514 32 | DefaultLogLevel = log.DEBUG 33 | ) 34 | 35 | func GetDefaultBindAddr() string { 36 | return fmt.Sprintf("%s:%d-%d", DefaultHost, DefaultMinPort, DefaultMaxPort) 37 | } 38 | -------------------------------------------------------------------------------- /config/uuid.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "github.com/skynetservices/skynet/log" 7 | "io" 8 | ) 9 | 10 | // NewUUID() provides unique identifier strings. 11 | func NewUUID() string { 12 | b := make([]byte, 16) 13 | _, err := io.ReadFull(rand.Reader, b) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | b[6] = (b[6] & 0x0F) | 0x40 18 | b[8] = (b[8] &^ 0x40) | 0x80 19 | return fmt.Sprintf("%x-%x-%x-%x-%x", b[:4], b[4:6], b[6:8], b[8:10], b[10:]) 20 | } 21 | -------------------------------------------------------------------------------- /criteria.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | type CriteriaMatcher interface { 4 | Matches(s ServiceInfo) bool 5 | } 6 | 7 | type Criteria struct { 8 | Hosts []string 9 | Regions []string 10 | Instances []string 11 | Services []ServiceCriteria 12 | Registered *bool 13 | } 14 | 15 | type ServiceCriteria struct { 16 | Name string 17 | Version string 18 | } 19 | 20 | func (sc *ServiceCriteria) String() string { 21 | if sc.Version == "" { 22 | return sc.Name 23 | } 24 | 25 | return sc.Name + ":" + sc.Version 26 | } 27 | 28 | func (sc *ServiceCriteria) Matches(name, version string) bool { 29 | if sc.Name != "" && sc.Name != name { 30 | return false 31 | } 32 | 33 | if sc.Version != "" && sc.Version != version { 34 | return false 35 | } 36 | 37 | return true 38 | } 39 | 40 | func (c *Criteria) Matches(s ServiceInfo) bool { 41 | if c.Instances != nil && len(c.Instances) > 0 && !exists(c.Instances, s.UUID) { 42 | return false 43 | } 44 | 45 | if c.Registered != nil && s.Registered != *c.Registered { 46 | return false 47 | } 48 | 49 | // If no hosts were provided we assume any hosts match 50 | if c.Hosts != nil && len(c.Hosts) > 0 && !exists(c.Hosts, s.ServiceAddr.IPAddress) { 51 | return false 52 | } 53 | 54 | // If no regions were provided we assume any regions match 55 | 56 | if c.Regions != nil && len(c.Regions) > 0 && !exists(c.Regions, s.Region) { 57 | return false 58 | } 59 | 60 | // Check for service match 61 | 62 | if c.Services != nil && len(c.Services) > 0 { 63 | match := false 64 | for _, sc := range c.Services { 65 | if sc.Matches(s.Name, s.Version) { 66 | match = true 67 | break 68 | } 69 | } 70 | 71 | if !match { 72 | return false 73 | } 74 | } 75 | 76 | return true 77 | } 78 | 79 | func (c *Criteria) AddInstance(uuid string) { 80 | if !exists(c.Instances, uuid) { 81 | c.Instances = append(c.Instances, uuid) 82 | } 83 | } 84 | 85 | func (c *Criteria) AddHost(host string) { 86 | if !exists(c.Hosts, host) { 87 | c.Hosts = append(c.Hosts, host) 88 | } 89 | } 90 | 91 | func (c *Criteria) AddRegion(region string) { 92 | if !exists(c.Regions, region) { 93 | c.Regions = append(c.Regions, region) 94 | } 95 | } 96 | 97 | func (c *Criteria) AddService(service ServiceCriteria) { 98 | for _, s := range c.Services { 99 | if s.Name == service.Name && s.Version == service.Version { 100 | return 101 | } 102 | } 103 | 104 | c.Services = append(c.Services, service) 105 | } 106 | 107 | // Returns a copy of this criteria 108 | func (c *Criteria) Clone() *Criteria { 109 | criteria := new(Criteria) 110 | copy(c.Hosts, criteria.Hosts) 111 | copy(c.Regions, criteria.Regions) 112 | copy(c.Instances, criteria.Instances) 113 | copy(c.Services, criteria.Services) 114 | 115 | return criteria 116 | } 117 | 118 | func exists(haystack []string, needle string) bool { 119 | for _, v := range haystack { 120 | if v == needle { 121 | return true 122 | } 123 | } 124 | 125 | return false 126 | } 127 | -------------------------------------------------------------------------------- /criteria_test.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type matchTestCase struct { 8 | Criteria Criteria 9 | MatchingInstances []ServiceInfo 10 | NonMatchingInstances []ServiceInfo 11 | } 12 | 13 | var testCases []matchTestCase = []matchTestCase{ 14 | matchTestCase{ 15 | Criteria: Criteria{ 16 | Regions: []string{ 17 | "Tampa", 18 | }, 19 | }, 20 | MatchingInstances: []ServiceInfo{ 21 | ServiceInfo{ 22 | Region: "Tampa", 23 | }, 24 | }, 25 | NonMatchingInstances: []ServiceInfo{ 26 | ServiceInfo{ 27 | Region: "Chicago", 28 | }, 29 | ServiceInfo{ 30 | Region: "Dallas", 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | func TestMatch(t *testing.T) { 37 | for _, tc := range testCases { 38 | for _, i := range tc.MatchingInstances { 39 | if !tc.Criteria.Matches(i) { 40 | t.Fatal("Instance expected to match criteria and did not") 41 | } 42 | } 43 | 44 | for _, i := range tc.NonMatchingInstances { 45 | if tc.Criteria.Matches(i) { 46 | t.Fatal("Instance should not match criteria") 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /daemon/client.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/client" 6 | ) 7 | 8 | type Client struct { 9 | client.ServiceClientProvider 10 | requestInfo *skynet.RequestInfo 11 | } 12 | 13 | func GetDaemonForService(s *skynet.ServiceInfo) (c Client) { 14 | return GetDaemonForHost(s.ServiceAddr.IPAddress) 15 | } 16 | 17 | func GetDaemonForHost(host string) (c Client) { 18 | registered := true 19 | criteria := &skynet.Criteria{ 20 | Hosts: []string{host}, 21 | Registered: ®istered, 22 | Services: []skynet.ServiceCriteria{ 23 | skynet.ServiceCriteria{Name: "SkynetDaemon"}, 24 | }, 25 | } 26 | 27 | s := client.GetServiceFromCriteria(criteria) 28 | c = Client{s, nil} 29 | return 30 | } 31 | 32 | func (c Client) ListSubServices(in ListSubServicesRequest) (out ListSubServicesResponse, err error) { 33 | err = c.Send(c.requestInfo, "ListSubServices", in, &out) 34 | return 35 | } 36 | 37 | func (c Client) StopAllSubServices(in StopAllSubServicesRequest) (out StopAllSubServicesResponse, err error) { 38 | err = c.Send(c.requestInfo, "StopAllSubServices", in, &out) 39 | return 40 | } 41 | 42 | func (c Client) StartSubService(in StartSubServiceRequest) (out StartSubServiceResponse, err error) { 43 | err = c.Send(c.requestInfo, "StartSubService", in, &out) 44 | return 45 | } 46 | 47 | func (c Client) StopSubService(in StopSubServiceRequest) (out StopSubServiceResponse, err error) { 48 | err = c.Send(c.requestInfo, "StopSubService", in, &out) 49 | return 50 | } 51 | 52 | func (c Client) RestartSubService(in RestartSubServiceRequest) (out RestartSubServiceResponse, err error) { 53 | err = c.Send(c.requestInfo, "RestartSubService", in, &out) 54 | return 55 | } 56 | 57 | func (c Client) RestartAllSubServices(in RestartAllSubServicesRequest) (out RestartAllSubServicesResponse, err error) { 58 | err = c.Send(c.requestInfo, "RestartAllSubServices", in, &out) 59 | return 60 | } 61 | 62 | func (c Client) RegisterSubService(in RegisterSubServiceRequest) (out RegisterSubServiceResponse, err error) { 63 | err = c.Send(c.requestInfo, "RegisterSubService", in, &out) 64 | return 65 | } 66 | 67 | func (c Client) UnregisterSubService(in UnregisterSubServiceRequest) (out UnregisterSubServiceResponse, err error) { 68 | err = c.Send(c.requestInfo, "UnregisterSubService", in, &out) 69 | return 70 | } 71 | 72 | func (c Client) SubServiceLogLevel(in SubServiceLogLevelRequest) (out SubServiceLogLevelResponse, err error) { 73 | err = c.Send(c.requestInfo, "SubServiceLogLevel", in, &out) 74 | return 75 | } 76 | 77 | func (c Client) LogLevel(in LogLevelRequest) (out LogLevelResponse, err error) { 78 | err = c.Send(c.requestInfo, "LogLevel", in, &out) 79 | return 80 | } 81 | 82 | func (c Client) Stop(in StopRequest) (out StopResponse, err error) { 83 | err = c.Send(c.requestInfo, "Stop", in, &out) 84 | c.Close() 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /daemon/messages.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | type SubServiceInfo struct { 4 | UUID string 5 | ServicePath string 6 | Args string 7 | Running bool 8 | } 9 | 10 | type ListSubServicesRequest struct { 11 | } 12 | 13 | type ListSubServicesResponse struct { 14 | Services map[string]SubServiceInfo 15 | } 16 | 17 | type StopAllSubServicesRequest struct { 18 | } 19 | 20 | type StopAllSubServicesResponse struct { 21 | Count int 22 | Stops []StopSubServiceResponse 23 | } 24 | 25 | type StartSubServiceRequest struct { 26 | BinaryName string 27 | Args string 28 | Registered bool 29 | } 30 | 31 | type StartSubServiceResponse struct { 32 | Ok bool 33 | UUID string 34 | } 35 | 36 | type StopSubServiceRequest struct { 37 | UUID string 38 | } 39 | 40 | type StopSubServiceResponse struct { 41 | Ok bool 42 | UUID string 43 | } 44 | 45 | type RestartSubServiceRequest struct { 46 | UUID string 47 | } 48 | 49 | type RestartSubServiceResponse struct { 50 | UUID string 51 | } 52 | 53 | type RestartAllSubServicesRequest struct { 54 | } 55 | 56 | type RestartAllSubServicesResponse struct { 57 | Count int 58 | Restarts []RestartSubServiceResponse 59 | } 60 | 61 | type RegisterSubServiceRequest struct { 62 | UUID string 63 | } 64 | 65 | type RegisterSubServiceResponse struct { 66 | Ok bool 67 | UUID string 68 | } 69 | 70 | type UnregisterSubServiceRequest struct { 71 | UUID string 72 | } 73 | 74 | type UnregisterSubServiceResponse struct { 75 | Ok bool 76 | UUID string 77 | } 78 | 79 | type SubServiceLogLevelRequest struct { 80 | UUID string 81 | Level string 82 | } 83 | 84 | type SubServiceLogLevelResponse struct { 85 | Ok bool 86 | UUID string 87 | Level string 88 | } 89 | 90 | type LogLevelRequest struct { 91 | Level string 92 | } 93 | 94 | type LogLevelResponse struct { 95 | Ok bool 96 | Level string 97 | } 98 | 99 | type StopRequest struct { 100 | } 101 | 102 | type StopResponse struct { 103 | Ok bool 104 | } 105 | -------------------------------------------------------------------------------- /daemon/pipe.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | const MAX_PIPE_BYTES = 32 8 | 9 | type Pipe struct { 10 | reader io.ReadCloser 11 | writer io.WriteCloser 12 | } 13 | 14 | func NewPipe(reader io.ReadCloser, writer io.WriteCloser) *Pipe { 15 | return &Pipe{ 16 | reader: reader, 17 | writer: writer, 18 | } 19 | } 20 | 21 | func (p *Pipe) Read(b []byte) (n int, err error) { 22 | return p.reader.Read(b) 23 | } 24 | 25 | func (p *Pipe) Write(b []byte) (n int, err error) { 26 | return p.writer.Write(b) 27 | } 28 | 29 | func (p *Pipe) Close() error { 30 | p.reader.Close() 31 | p.writer.Close() 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /documentation/SkyNetFavicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skynetservices/skynet-archive/c1e213743e42720a89c53bfc641cc03d9c172b78/documentation/SkyNetFavicon.png -------------------------------------------------------------------------------- /documentation/SkyNetLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skynetservices/skynet-archive/c1e213743e42720a89c53bfc641cc03d9c172b78/documentation/SkyNetLogo.png -------------------------------------------------------------------------------- /documentation/SkynetIconv1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skynetservices/skynet-archive/c1e213743e42720a89c53bfc641cc03d9c172b78/documentation/SkynetIconv1.png -------------------------------------------------------------------------------- /documentation/SkynetIconv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skynetservices/skynet-archive/c1e213743e42720a89c53bfc641cc03d9c172b78/documentation/SkynetIconv2.png -------------------------------------------------------------------------------- /documentation/protocol.md: -------------------------------------------------------------------------------- 1 | # skynet client/server protocol 2 | 3 | ## Types 4 | 5 | ClientHandshake 6 | (defined in github.com/skynetservices/skynet ClientHandshake type) 7 | { 8 | 9 | } 10 | 11 | ServiceHandshake 12 | (defined in github.com/skynetservices/skynet ServiceHandshake type) 13 | { 14 | Registered bool 15 | ClientID string 16 | } 17 | 18 | RequestHeader 19 | (defined in net/rpc Request type) 20 | { 21 | ServiceMethod string 22 | Seq uint64 23 | } 24 | 25 | ResponseHeader 26 | (defined in net/rpc Response type) 27 | { 28 | ServiceMethod string 29 | Seq uint64 30 | Error string 31 | } 32 | 33 | RequestInfo 34 | (defined in github.com/skynetservices/skynet RequestInfo type) 35 | { 36 | // OriginAddress is the reported address of the originating client, typically from outside the service cluster. 37 | OriginAddress string 38 | // RequestID is a unique ID for the current RPC request. 39 | RequestID string 40 | // RetryCount indicates how many times this request has been tried before. 41 | RetryCount int 42 | } 43 | 44 | RequestIn 45 | (defined in github.com/skynetservices/skynet ServiceRPCIn type) 46 | { 47 | ClientID string 48 | Method string 49 | RequestInfo RequestInfo 50 | In []byte 51 | } 52 | 53 | RequestOut 54 | (defined in github.com/skynetservices/skynet ServiceRPCOut type) 55 | { 56 | Out []byte 57 | ErrString string 58 | } 59 | 60 | ## skynet protocol 61 | 62 | 1) Client/server handshake 63 | 64 | Service: **ServiceHandshake** 65 | * **Registered**: A value of false indicates the service will not respond to requests. 66 | * **ClientID**: A UUID that must be provided will all requests. 67 | 68 | Client: **ClientHandshake** 69 | 70 | 2) Client may begin sending requests. When done sending requests, the stream may be closed by the client. 71 | 72 | Client: **RequestHeader** 73 | * **ServiceMethod**: Use "**Name**.Forward", where **Name** is the service's reported name. 74 | * **Seq**: Use a number unique to this session, usually by incrementing some counter. 75 | 76 | Client: **RequestIn** 77 | * **ClientID**: Must be the UUID provided by the **ServiceHandshake**. 78 | * **Method**: The name of the RPC method desired. 79 | * **RequestInfo**.**RequestID**: A UUID. If this is request is the direct result of another request, the UUID may be reused. 80 | * **RequestInfo**.**OriginAddress**: If this request originated from another machine, that machine's address may be used. If left blank, the service will fill it in with the client's remote address. 81 | * **In**: The BSON-encoded buffer representing the RPC's in parameter. 82 | 83 | 3) Service may synchronously send responses, in any order as long as the response corresponds to a request sent by the client. When the stream is closed by the client and all responses have been issued, the stream may be closed by the service. 84 | 85 | Service: **ResponseHeader** 86 | * **ServiceMethod**: Will be the same "**Name**.Forward" provided in the request. 87 | * **Seq**: This number will match the **Seq** provided in the request to which this response corresponds. 88 | * **Error**: Any rpc-level or skynet-level error. Empty string if no error. Errors in the actual service call are not put here. 89 | 90 | Service: **RequestOut** 91 | * **Out**: The BSON-encoded buffer represending the RPC's out parameter. 92 | * **Error**: The text of the error returned by the service call, or the empty string if no error. 93 | -------------------------------------------------------------------------------- /handshake.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | // ServiceHandshake is data sent by the service to the client immediately once the connection 4 | // is opened. 5 | type ServiceHandshake struct { 6 | // Name indicates the service name, for validation on the client side 7 | Name string 8 | 9 | // Registered indicates the state of this service. If it is false, the connection will 10 | // close immediately and the client should look elsewhere for this service. 11 | Registered bool 12 | 13 | // ClientID is a UUID that is used by the client to identify itself in RPC requests. 14 | ClientID string 15 | } 16 | 17 | // ClientHandshake is sent by the client to the service after receipt of the ServiceHandshake. 18 | type ClientHandshake struct { 19 | ClientID string 20 | } 21 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | // Package log provides syslog logging to a local or remote 2 | // Syslog logger. To specify a remote syslog host, set the 3 | // "log.sysloghost" key in the Skynet configuration. Specify 4 | // the port with "log.syslogport". If "log.sysloghost" is not provided, 5 | // skynet will log to local syslog. 6 | package log 7 | 8 | import ( 9 | "fmt" 10 | "log/syslog" 11 | "strconv" 12 | ) 13 | 14 | type LogLevel int8 15 | 16 | var syslogHost string 17 | var syslogPort int = 0 18 | 19 | var minLevel LogLevel 20 | var logger *syslog.Writer 21 | 22 | const ( 23 | TRACE LogLevel = iota 24 | DEBUG 25 | INFO 26 | WARN 27 | ERROR 28 | FATAL 29 | PANIC 30 | ) 31 | 32 | // Call Initialize after setting (or not setting) SyslogHost and SyslogPort when 33 | // they're read from configuration source. 34 | func Initialize() { 35 | 36 | var e error 37 | 38 | if len(syslogHost) > 0 { 39 | 40 | logger, e = syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "skynet") 41 | if e != nil { 42 | panic(e) 43 | } 44 | } else { 45 | logger, e = syslog.Dial("tcp4", syslogHost+":"+strconv.Itoa(syslogPort), syslog.LOG_INFO|syslog.LOG_USER, "skynet") 46 | if e != nil { 47 | panic(e) 48 | } 49 | } 50 | 51 | } 52 | 53 | func Panic(messages ...interface{}) { 54 | logger.Emerg(fromMulti(messages)) 55 | } 56 | 57 | func Panicf(format string, messages ...interface{}) { 58 | m := fmt.Sprintf(format, messages...) 59 | logger.Emerg(m) 60 | } 61 | 62 | func Fatal(messages ...interface{}) { 63 | if minLevel <= FATAL { 64 | logger.Crit(fromMulti(messages)) 65 | } 66 | } 67 | 68 | func Fatalf(format string, messages ...interface{}) { 69 | if minLevel <= FATAL { 70 | m := fmt.Sprintf(format, messages...) 71 | logger.Crit(m) 72 | } 73 | } 74 | 75 | func Error(messages ...interface{}) { 76 | if minLevel <= ERROR { 77 | logger.Err(fromMulti(messages)) 78 | } 79 | } 80 | 81 | func Errorf(format string, messages ...interface{}) { 82 | if minLevel <= ERROR { 83 | m := fmt.Sprintf(format, messages...) 84 | logger.Err(m) 85 | } 86 | } 87 | 88 | func Warn(messages ...interface{}) { 89 | if minLevel <= WARN { 90 | logger.Warning(fromMulti(messages)) 91 | } 92 | } 93 | 94 | func Warnf(format string, messages ...interface{}) { 95 | if minLevel <= WARN { 96 | m := fmt.Sprintf(format, messages...) 97 | logger.Warning(m) 98 | } 99 | } 100 | 101 | func Info(messages ...interface{}) { 102 | if minLevel <= INFO { 103 | logger.Info(fromMulti(messages)) 104 | } 105 | } 106 | 107 | func Infof(format string, messages ...interface{}) { 108 | if minLevel <= INFO { 109 | m := fmt.Sprintf(format, messages...) 110 | logger.Info(m) 111 | } 112 | } 113 | 114 | func Debug(messages ...interface{}) { 115 | if minLevel <= DEBUG { 116 | logger.Debug(fromMulti(messages)) 117 | } 118 | } 119 | 120 | func Debugf(format string, messages ...interface{}) { 121 | if minLevel <= DEBUG { 122 | m := fmt.Sprintf(format, messages...) 123 | logger.Debug(m) 124 | } 125 | } 126 | 127 | func Trace(messages ...interface{}) { 128 | if minLevel <= TRACE { 129 | logger.Debug(fromMulti(messages)) 130 | } 131 | } 132 | 133 | func Tracef(format string, messages ...interface{}) { 134 | if minLevel <= TRACE { 135 | m := fmt.Sprintf(format, messages...) 136 | logger.Debug(m) 137 | } 138 | } 139 | 140 | func Println(level LogLevel, messages ...interface{}) { 141 | 142 | switch level { 143 | case DEBUG: 144 | Debugf("%v", messages) 145 | case TRACE: 146 | Tracef("%v", messages) 147 | case INFO: 148 | Infof("%v", messages) 149 | case WARN: 150 | Warnf("%v", messages) 151 | case ERROR: 152 | Errorf("%v", messages) 153 | case FATAL: 154 | Fatalf("%v", messages) 155 | case PANIC: 156 | Panicf("%v", messages) 157 | } 158 | 159 | return 160 | } 161 | 162 | func Printf(level LogLevel, format string, messages ...interface{}) { 163 | 164 | switch level { 165 | case DEBUG: 166 | Debugf(format, messages) 167 | case TRACE: 168 | Tracef(format, messages) 169 | case INFO: 170 | Infof(format, messages) 171 | case WARN: 172 | Warnf(format, messages) 173 | case ERROR: 174 | Errorf(format, messages) 175 | case FATAL: 176 | Fatalf(format, messages) 177 | case PANIC: 178 | Panicf(format, messages) 179 | } 180 | 181 | return 182 | } 183 | 184 | func SetSyslogHost(host string) { 185 | syslogHost = host 186 | } 187 | 188 | func SetSyslogPort(port int) { 189 | syslogPort = port 190 | } 191 | 192 | func SetLogLevel(level LogLevel) { 193 | minLevel = level 194 | } 195 | 196 | func GetLogLevel() LogLevel { 197 | return minLevel 198 | } 199 | 200 | func fromMulti(messages ...interface{}) string{ 201 | var r string 202 | for x :=0; x < len(messages); x++ { 203 | r = r + messages[x].(string) 204 | if x < len(messages) { 205 | r = r + " " 206 | } 207 | } 208 | return r 209 | } 210 | 211 | func LevelFromString(l string) (level LogLevel) { 212 | switch l { 213 | case "DEBUG": 214 | level = DEBUG 215 | case "TRACE": 216 | level = TRACE 217 | case "INFO": 218 | level = INFO 219 | case "WARN": 220 | level = WARN 221 | case "ERROR": 222 | level = ERROR 223 | case "FATAL": 224 | level = FATAL 225 | case "PANIC": 226 | level = PANIC 227 | } 228 | 229 | return 230 | } 231 | -------------------------------------------------------------------------------- /log/multiwriter.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type MultiWriter struct { 8 | writers []io.Writer 9 | } 10 | 11 | func (mw *MultiWriter) Write(p []byte) (n int, err error) { 12 | for _, w := range mw.writers { 13 | n, err = w.Write(p) 14 | 15 | if err != nil { 16 | return 17 | } 18 | } 19 | 20 | return 21 | } 22 | 23 | func NewMultiWriter(writers ...io.Writer) *MultiWriter { 24 | return &MultiWriter{ 25 | writers: writers, 26 | } 27 | } 28 | 29 | func (mw *MultiWriter) AddWriter(w io.Writer) { 30 | mw.writers = append(mw.writers, w) 31 | } 32 | -------------------------------------------------------------------------------- /logmessages.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type ServiceDiscovered struct { 8 | Service *ServiceInfo 9 | } 10 | 11 | func (sd ServiceDiscovered) String() string { 12 | return fmt.Sprintf("Discovered service %q at %s", sd.Service.Name, 13 | sd.Service.ServiceAddr) 14 | } 15 | 16 | type ServiceRemoved struct { 17 | Service *ServiceInfo 18 | } 19 | 20 | func (sr ServiceRemoved) String() string { 21 | return fmt.Sprintf("Removed service %q at %s", sr.Service.Name, 22 | sr.Service.ServiceAddr) 23 | } 24 | 25 | type ServiceCreated struct { 26 | ServiceInfo *ServiceInfo 27 | } 28 | 29 | func (sc ServiceCreated) String() string { 30 | return fmt.Sprintf("Created service %q", sc.ServiceInfo.Name) 31 | } 32 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | import ( 4 | "labix.org/v2/mgo/bson" 5 | ) 6 | 7 | type RegisterRequest struct { 8 | } 9 | 10 | type RegisterResponse struct { 11 | } 12 | 13 | type UnregisterRequest struct { 14 | } 15 | 16 | type UnregisterResponse struct { 17 | } 18 | 19 | type StopRequest struct { 20 | WaitForClients bool 21 | } 22 | 23 | type StopResponse struct { 24 | } 25 | 26 | type ServiceRPCInRead struct { 27 | ClientID string 28 | Method string 29 | RequestInfo *RequestInfo 30 | In []byte 31 | } 32 | 33 | type ServiceRPCInWrite struct { 34 | ClientID string 35 | Method string 36 | RequestInfo *RequestInfo 37 | In bson.Binary 38 | } 39 | 40 | type ServiceRPCOutRead struct { 41 | Out []byte 42 | ErrString string 43 | } 44 | 45 | type ServiceRPCOutWrite struct { 46 | Out bson.Binary 47 | ErrString string 48 | } 49 | -------------------------------------------------------------------------------- /pools/resourcepool.go: -------------------------------------------------------------------------------- 1 | package pools 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type Resource interface { 8 | Close() 9 | IsClosed() bool 10 | } 11 | 12 | type Factory func() (Resource, error) 13 | 14 | type ResourcePool struct { 15 | factory Factory 16 | idleResources ring 17 | idleCapacity int 18 | maxResources int 19 | numResources int 20 | 21 | acqchan chan acquireMessage 22 | rchan chan releaseMessage 23 | cchan chan closeMessage 24 | 25 | activeWaits []acquireMessage 26 | } 27 | 28 | func NewSourcelessPool() (rp *ResourcePool) { 29 | return NewResourcePool(func() (Resource, error) { return nil, nil }, -1, 0) 30 | } 31 | 32 | func NewResourcePool(factory Factory, idleCapacity, maxResources int) (rp *ResourcePool) { 33 | rp = &ResourcePool{ 34 | factory: factory, 35 | idleCapacity: idleCapacity, 36 | maxResources: maxResources, 37 | 38 | acqchan: make(chan acquireMessage), 39 | rchan: make(chan releaseMessage, 1), 40 | cchan: make(chan closeMessage, 1), 41 | } 42 | 43 | go rp.mux() 44 | 45 | return 46 | } 47 | 48 | type releaseMessage struct { 49 | r Resource 50 | } 51 | 52 | type acquireMessage struct { 53 | rch chan Resource 54 | ech chan error 55 | } 56 | 57 | type closeMessage struct { 58 | } 59 | 60 | func (rp *ResourcePool) mux() { 61 | loop: 62 | for { 63 | select { 64 | case acq := <-rp.acqchan: 65 | rp.acquire(acq) 66 | case rel := <-rp.rchan: 67 | if len(rp.activeWaits) != 0 { 68 | // someone is waiting - give them the resource if we can 69 | if !rel.r.IsClosed() { 70 | rp.activeWaits[0].rch <- rel.r 71 | } else { 72 | // if we can't, discard the released resource and create a new one 73 | r, err := rp.factory() 74 | if err != nil { 75 | // reflect the smaller number of existant resources 76 | rp.numResources-- 77 | rp.activeWaits[0].ech <- err 78 | } else { 79 | rp.activeWaits[0].rch <- r 80 | } 81 | } 82 | rp.activeWaits = rp.activeWaits[1:] 83 | } else { 84 | // if no one is waiting, release it for idling or closing 85 | rp.release(rel.r) 86 | } 87 | 88 | case _ = <-rp.cchan: 89 | break loop 90 | } 91 | } 92 | for !rp.idleResources.Empty() { 93 | rp.idleResources.Dequeue().Close() 94 | } 95 | for _, aw := range rp.activeWaits { 96 | aw.ech <- errors.New("Resource pool closed") 97 | } 98 | } 99 | 100 | func (rp *ResourcePool) acquire(acq acquireMessage) { 101 | for !rp.idleResources.Empty() { 102 | r := rp.idleResources.Dequeue() 103 | if !r.IsClosed() { 104 | acq.rch <- r 105 | return 106 | } 107 | // discard closed resources 108 | rp.numResources-- 109 | } 110 | if rp.maxResources != -1 && rp.numResources >= rp.maxResources { 111 | // we need to wait until something comes back in 112 | rp.activeWaits = append(rp.activeWaits, acq) 113 | return 114 | } 115 | 116 | r, err := rp.factory() 117 | if err != nil { 118 | acq.ech <- err 119 | } else { 120 | rp.numResources++ 121 | acq.rch <- r 122 | } 123 | 124 | return 125 | } 126 | 127 | func (rp *ResourcePool) release(resource Resource) { 128 | if resource == nil || resource.IsClosed() { 129 | // don't put it back in the pool. 130 | rp.numResources-- 131 | return 132 | } 133 | if rp.idleCapacity != -1 && rp.idleResources.Size() == rp.idleCapacity { 134 | resource.Close() 135 | rp.numResources-- 136 | return 137 | } 138 | 139 | rp.idleResources.Enqueue(resource) 140 | } 141 | 142 | // Acquire() will get one of the idle resources, or create a new one. 143 | func (rp *ResourcePool) Acquire() (resource Resource, err error) { 144 | acq := acquireMessage{ 145 | rch: make(chan Resource), 146 | ech: make(chan error), 147 | } 148 | rp.acqchan <- acq 149 | 150 | select { 151 | case resource = <-acq.rch: 152 | case err = <-acq.ech: 153 | } 154 | 155 | return 156 | } 157 | 158 | // Release() will release a resource for use by others. If the idle queue is 159 | // full, the resource will be closed. 160 | func (rp *ResourcePool) Release(resource Resource) { 161 | rel := releaseMessage{ 162 | r: resource, 163 | } 164 | rp.rchan <- rel 165 | } 166 | 167 | // Close() closes all the pools resources. 168 | func (rp *ResourcePool) Close() { 169 | rp.cchan <- closeMessage{} 170 | } 171 | 172 | // NumResources() the number of resources known at this time 173 | func (rp *ResourcePool) NumResources() int { 174 | return rp.numResources 175 | } 176 | -------------------------------------------------------------------------------- /pools/ring.go: -------------------------------------------------------------------------------- 1 | package pools 2 | 3 | type ring struct { 4 | cnt, i int 5 | data []Resource 6 | } 7 | 8 | func (rb *ring) Size() int { 9 | return rb.cnt 10 | } 11 | 12 | func (rb *ring) Empty() bool { 13 | return rb.cnt == 0 14 | } 15 | 16 | func (rb *ring) Peek() Resource { 17 | return rb.data[rb.i] 18 | } 19 | 20 | func (rb *ring) Enqueue(x Resource) { 21 | if rb.cnt >= len(rb.data) { 22 | rb.grow(2*rb.cnt + 1) 23 | } 24 | rb.data[(rb.i+rb.cnt)%len(rb.data)] = x 25 | rb.cnt++ 26 | } 27 | 28 | func (rb *ring) Dequeue() (x Resource) { 29 | x = rb.Peek() 30 | rb.cnt, rb.i = rb.cnt-1, (rb.i+1)%len(rb.data) 31 | return 32 | } 33 | 34 | func (rb *ring) grow(newSize int) { 35 | newData := make([]Resource, newSize) 36 | 37 | n := copy(newData, rb.data[rb.i:]) 38 | copy(newData[n:], rb.data[:rb.cnt-n]) 39 | 40 | rb.i = 0 41 | rb.data = newData 42 | } 43 | -------------------------------------------------------------------------------- /requestinfo.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | // RequestInfo is information about a request, and is provided to every skynet RPC call. 4 | type RequestInfo struct { 5 | // OriginAddress is the reported address of the originating client, typically from outside the service cluster. 6 | OriginAddress string 7 | // ConnectionAddress is the address of the TCP connection making the current RPC request. 8 | ConnectionAddress string 9 | // RequestID is a unique ID for the current RPC request. 10 | RequestID string 11 | // RetryCount indicates how many times this request has been tried before. 12 | RetryCount int 13 | } 14 | -------------------------------------------------------------------------------- /rpc/bsonrpc/bsoncoders.go: -------------------------------------------------------------------------------- 1 | package bsonrpc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/skynetservices/skynet/log" 6 | "io" 7 | "labix.org/v2/mgo/bson" 8 | ) 9 | 10 | type Encoder struct { 11 | w io.Writer 12 | } 13 | 14 | func NewEncoder(w io.Writer) *Encoder { 15 | return &Encoder{w: w} 16 | } 17 | 18 | func (e *Encoder) Encode(v interface{}) (err error) { 19 | buf, err := bson.Marshal(v) 20 | if err != nil { 21 | return 22 | } 23 | 24 | n, err := e.w.Write(buf) 25 | if err != nil { 26 | return 27 | } 28 | 29 | if l := len(buf); n != l { 30 | err = fmt.Errorf("Wrote %d bytes, should have wrote %d", n, l) 31 | } 32 | 33 | log.Println(log.TRACE, fmt.Sprintf("RPC Wrote %d bytes of %d to connection from buffer: ", n, len(buf)), buf) 34 | 35 | return 36 | } 37 | 38 | type Decoder struct { 39 | r io.Reader 40 | } 41 | 42 | func NewDecoder(r io.Reader) *Decoder { 43 | return &Decoder{r: r} 44 | } 45 | 46 | func (d *Decoder) Decode(pv interface{}) (err error) { 47 | var lbuf [4]byte 48 | n, err := d.r.Read(lbuf[:]) 49 | 50 | if n != 4 { 51 | err = fmt.Errorf("Corrupted BSON stream: could only read %d", n) 52 | return 53 | } 54 | 55 | log.Println(log.TRACE, fmt.Sprintf("Read %d bytes of 4 byte length from connection, received bytes: ", n), lbuf) 56 | 57 | if err != nil { 58 | return 59 | } 60 | 61 | length := (int(lbuf[0]) << 0) | 62 | (int(lbuf[1]) << 8) | 63 | (int(lbuf[2]) << 16) | 64 | (int(lbuf[3]) << 24) 65 | 66 | log.Println(log.TRACE, "Message length parsed as: ", length) 67 | 68 | buf := make([]byte, length) 69 | copy(buf[0:4], lbuf[:]) 70 | 71 | n, err = io.ReadFull(d.r, buf[4:]) 72 | 73 | log.Println(log.TRACE, fmt.Sprintf("Read %d bytes of %d from connection, received bytes: ", n+4, length), buf) 74 | 75 | if err != nil { 76 | return 77 | } 78 | 79 | if n+4 != length { 80 | err = fmt.Errorf("Expected %d bytes, read %d", length, n) 81 | return 82 | } 83 | 84 | if pv != nil { 85 | err = bson.Unmarshal(buf, pv) 86 | } 87 | 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /rpc/bsonrpc/bsoncoders_test.go: -------------------------------------------------------------------------------- 1 | package bsonrpc 2 | 3 | import ( 4 | "bytes" 5 | "labix.org/v2/mgo/bson" 6 | "net/rpc" 7 | "testing" 8 | ) 9 | 10 | func TestEncode(t *testing.T) { 11 | } 12 | 13 | func TestDecode(t *testing.T) { 14 | req := rpc.Request{ 15 | ServiceMethod: "Foo.Bar", 16 | Seq: 3, 17 | } 18 | 19 | b, err := bson.Marshal(req) 20 | 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | buf := bytes.NewBuffer(b) 26 | dec := NewDecoder(buf) 27 | 28 | r := new(rpc.Request) 29 | err = dec.Decode(r) 30 | 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | if *r != req { 36 | t.Fatal("Values don't match") 37 | } 38 | } 39 | 40 | func TestDecodeReadsOnlyOne(t *testing.T) { 41 | req := rpc.Request{ 42 | ServiceMethod: "Foo.Bar", 43 | Seq: 3, 44 | } 45 | 46 | type T struct { 47 | Value string 48 | } 49 | 50 | tv := T{"test"} 51 | 52 | b, err := bson.Marshal(req) 53 | 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | buf := bytes.NewBuffer(b) 59 | 60 | b, err = bson.Marshal(tv) 61 | 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | buf.Write(b) 67 | dec := NewDecoder(buf) 68 | 69 | r := new(rpc.Request) 70 | err = dec.Decode(r) 71 | 72 | if *r != req { 73 | t.Fatal("Values don't match") 74 | } 75 | 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | // We should be able to read a second message off this io.Reader 81 | tmp := new(T) 82 | err = dec.Decode(tmp) 83 | 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | if *tmp != tv { 89 | t.Fatal("Values don't match") 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /rpc/bsonrpc/client.go: -------------------------------------------------------------------------------- 1 | package bsonrpc 2 | 3 | import ( 4 | "errors" 5 | "github.com/kr/pretty" 6 | "github.com/skynetservices/skynet/log" 7 | "io" 8 | "net/rpc" 9 | "reflect" 10 | ) 11 | 12 | type ClientCodec struct { 13 | conn io.ReadWriteCloser 14 | Encoder *Encoder 15 | Decoder *Decoder 16 | } 17 | 18 | func NewClientCodec(conn io.ReadWriteCloser) (codec *ClientCodec) { 19 | cc := &ClientCodec{ 20 | conn: conn, 21 | Encoder: NewEncoder(conn), 22 | Decoder: NewDecoder(conn), 23 | } 24 | codec = cc 25 | return 26 | } 27 | 28 | func (cc *ClientCodec) WriteRequest(req *rpc.Request, v interface{}) (err error) { 29 | log.Println(log.TRACE, "RPC Client Entered: WriteRequest") 30 | defer log.Println(log.TRACE, "RPC Client Leaving: WriteRequest") 31 | 32 | log.Println(log.TRACE, pretty.Sprintf("RPC Client Writing RequestHeader %s %+v", reflect.TypeOf(req), req)) 33 | 34 | err = cc.Encoder.Encode(req) 35 | if err != nil { 36 | log.Println(log.ERROR, "RPC Client Error enconding request rpc request: ", err) 37 | cc.Close() 38 | return 39 | } 40 | 41 | log.Println(log.TRACE, pretty.Sprintf("RPC Client Writing Request Value %s %+v", reflect.TypeOf(v), v)) 42 | 43 | err = cc.Encoder.Encode(v) 44 | if err != nil { 45 | log.Println(log.ERROR, "RPC Client Error enconding request value: ", err) 46 | cc.Close() 47 | return 48 | } 49 | 50 | return 51 | } 52 | 53 | func (cc *ClientCodec) ReadResponseHeader(res *rpc.Response) (err error) { 54 | log.Println(log.TRACE, "RPC Client Entered: ReadResponseHeader") 55 | defer log.Println(log.TRACE, "RPC Client Leaving: ReadResponseHeader") 56 | 57 | err = cc.Decoder.Decode(res) 58 | 59 | if err != nil { 60 | cc.Close() 61 | log.Println(log.ERROR, "RPC Client Error decoding response header: ", err) 62 | } 63 | 64 | if err == nil { 65 | log.Println(log.TRACE, pretty.Sprintf("RPC Client Read ResponseHeader %s %+v", reflect.TypeOf(res), res)) 66 | } 67 | 68 | return 69 | } 70 | 71 | func (cc *ClientCodec) ReadResponseBody(v interface{}) (err error) { 72 | log.Println(log.TRACE, "RPC Client Entered: ReadResponseBody") 73 | defer log.Println(log.TRACE, "RPC Client Leaving: ReadResponseBody") 74 | 75 | if v == nil { 76 | err = errors.New("Response object cannot be nil") 77 | log.Println(log.ERROR, "RPC Client Error reading response body: ", err) 78 | return 79 | } 80 | 81 | err = cc.Decoder.Decode(v) 82 | 83 | if err != nil { 84 | cc.Close() 85 | log.Println(log.ERROR, "RPC Client Error decoding response body: ", err) 86 | } 87 | 88 | if err == nil { 89 | log.Println(log.TRACE, pretty.Sprintf("RPC Client Read ResponseBody %s %+v", reflect.TypeOf(v), v)) 90 | } 91 | return 92 | } 93 | 94 | func (cc *ClientCodec) Close() (err error) { 95 | log.Println(log.TRACE, "RPC Client Entered: Close") 96 | defer log.Println(log.TRACE, "RPC Client Leaving: Close") 97 | 98 | err = cc.conn.Close() 99 | 100 | if err != nil && err.Error() != "use of closed network connection" { 101 | log.Println(log.ERROR, "RPC Client Error closing connection: ", err) 102 | } 103 | 104 | return 105 | } 106 | 107 | func NewClient(conn io.ReadWriteCloser) (c *rpc.Client) { 108 | cc := NewClientCodec(conn) 109 | c = rpc.NewClientWithCodec(cc) 110 | return 111 | } 112 | -------------------------------------------------------------------------------- /rpc/bsonrpc/rpc_test.go: -------------------------------------------------------------------------------- 1 | package bsonrpc 2 | 3 | import ( 4 | "io" 5 | "net/rpc" 6 | "testing" 7 | ) 8 | 9 | type duplex struct { 10 | io.Reader 11 | io.Writer 12 | } 13 | 14 | func (d duplex) Close() (err error) { 15 | return 16 | } 17 | 18 | type TestParam struct { 19 | Val1 string 20 | Val2 int 21 | } 22 | 23 | type Test int 24 | 25 | func (ts Test) Foo(in TestParam, out *TestParam) (err error) { 26 | out.Val1 = in.Val1 + "world!" 27 | out.Val2 = in.Val2 + 5 28 | return 29 | } 30 | 31 | func TestBasicClientServer(t *testing.T) { 32 | toServer, fromClient := io.Pipe() 33 | toClient, fromServer := io.Pipe() 34 | 35 | s := rpc.NewServer() 36 | var ts Test 37 | s.Register(&ts) 38 | go s.ServeCodec(NewServerCodec(duplex{toServer, fromServer})) 39 | 40 | cl := NewClient(duplex{toClient, fromClient}) 41 | 42 | var tp TestParam 43 | tp.Val1 = "Hello " 44 | tp.Val2 = 10 45 | 46 | err := cl.Call("Test.Foo", tp, &tp) 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | if tp.Val1 != "Hello world!" { 52 | t.Errorf("tp.Val2: expected %q, got %q", "Hello world!", tp.Val1) 53 | } 54 | if tp.Val2 != 15 { 55 | t.Errorf("tp.Val2: expected 15, got %d", tp.Val2) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rpc/bsonrpc/server.go: -------------------------------------------------------------------------------- 1 | package bsonrpc 2 | 3 | import ( 4 | "github.com/kr/pretty" 5 | "github.com/skynetservices/skynet/log" 6 | "io" 7 | "net/rpc" 8 | "reflect" 9 | ) 10 | 11 | type ServerCodec struct { 12 | conn io.ReadWriteCloser 13 | Encoder *Encoder 14 | Decoder *Decoder 15 | } 16 | 17 | func NewServerCodec(conn io.ReadWriteCloser) (codec *ServerCodec) { 18 | codec = &ServerCodec{ 19 | conn: conn, 20 | Encoder: NewEncoder(conn), 21 | Decoder: NewDecoder(conn), 22 | } 23 | 24 | return 25 | } 26 | 27 | func (sc *ServerCodec) ReadRequestHeader(rq *rpc.Request) (err error) { 28 | log.Println(log.TRACE, "RPC Server Entered: ReadRequestHeader") 29 | defer log.Println(log.TRACE, "RPC Server Leaving: ReadRequestHeader") 30 | 31 | err = sc.Decoder.Decode(rq) 32 | if err != nil && err != io.EOF { 33 | log.Println(log.ERROR, "RPC Server Error decoding request header: ", err) 34 | sc.Close() 35 | } 36 | 37 | if err == nil { 38 | log.Println(log.TRACE, pretty.Sprintf("RPC Server Read RequestHeader %s %+v", reflect.TypeOf(rq), rq)) 39 | } 40 | return 41 | } 42 | 43 | func (sc *ServerCodec) ReadRequestBody(v interface{}) (err error) { 44 | log.Println(log.TRACE, "RPC Server Entered: ReadRequestBody") 45 | defer log.Println(log.TRACE, "RPC Server Leaving: ReadRequestBody") 46 | 47 | err = sc.Decoder.Decode(v) 48 | if err != nil { 49 | log.Println(log.ERROR, "RPC Server Error decoding request body: ", err) 50 | } 51 | 52 | if err == nil { 53 | log.Println(log.TRACE, pretty.Sprintf("RPC Server Read RequestBody %s %+v", reflect.TypeOf(v), v)) 54 | } 55 | return 56 | } 57 | 58 | func (sc *ServerCodec) WriteResponse(rs *rpc.Response, v interface{}) (err error) { 59 | log.Println(log.TRACE, "RPC Server Entered: WriteResponse") 60 | defer log.Println(log.TRACE, "RPC Server Leaving: WriteResponse") 61 | 62 | log.Println(log.TRACE, pretty.Sprintf("RPC Server Writing ResponseHeader %s %+v", reflect.TypeOf(rs), rs)) 63 | 64 | err = sc.Encoder.Encode(rs) 65 | if err != nil { 66 | log.Println(log.ERROR, "RPC Server Error encoding rpc response: ", err) 67 | sc.Close() 68 | return 69 | } 70 | 71 | log.Println(log.TRACE, pretty.Sprintf("RPC Server Writing Response Value %s %+v", reflect.TypeOf(v), v)) 72 | 73 | err = sc.Encoder.Encode(v) 74 | if err != nil { 75 | log.Println(log.ERROR, "RPC Server Error encoding response value: ", err) 76 | sc.Close() 77 | return 78 | } 79 | 80 | return 81 | } 82 | 83 | func (sc *ServerCodec) Close() (err error) { 84 | log.Println(log.TRACE, "RPC Server Entered: Close") 85 | defer log.Println(log.TRACE, "RPC Server Leaving: Close") 86 | 87 | err = sc.conn.Close() 88 | if err != nil && err.Error() != "use of closed network connection" { 89 | log.Println(log.ERROR, "RPC Server Error closing connection: ", err) 90 | return 91 | } 92 | return 93 | } 94 | 95 | func ServeConn(conn io.ReadWriteCloser) (s *rpc.Server) { 96 | s = rpc.NewServer() 97 | s.ServeCodec(NewServerCodec(conn)) 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /service/admin.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/log" 6 | ) 7 | 8 | type Admin struct { 9 | service *Service 10 | } 11 | 12 | func (sa *Admin) Register(in skynet.RegisterRequest, out *skynet.RegisterResponse) (err error) { 13 | log.Println(log.TRACE, "Got RPC admin command Register") 14 | sa.service.Register() 15 | return 16 | } 17 | 18 | func (sa *Admin) Unregister(in skynet.UnregisterRequest, out *skynet.UnregisterResponse) (err error) { 19 | log.Println(log.TRACE, "Got RPC admin command Unregister") 20 | sa.service.Unregister() 21 | return 22 | } 23 | 24 | func (sa *Admin) Stop(in skynet.StopRequest, out *skynet.StopResponse) (err error) { 25 | log.Println(log.TRACE, "Got RPC admin command Stop") 26 | 27 | sa.service.Shutdown() 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /service/logmessages.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "github.com/skynetservices/skynet" 6 | "syscall" 7 | "time" 8 | ) 9 | 10 | type RegisteredMethods struct { 11 | Methods []string 12 | } 13 | 14 | func (rm RegisteredMethods) String() string { 15 | return fmt.Sprintf("Registered methods: %v", rm.Methods) 16 | } 17 | 18 | type MethodCall struct { 19 | RequestInfo *skynet.RequestInfo 20 | MethodName string 21 | } 22 | 23 | func (mi MethodCall) String() string { 24 | return fmt.Sprintf("Method %q called with RequestInfo %v", mi.MethodName, mi.RequestInfo) 25 | } 26 | 27 | type MethodCompletion struct { 28 | RequestInfo *skynet.RequestInfo 29 | MethodName string 30 | Duration time.Duration 31 | } 32 | 33 | func (mi MethodCompletion) String() string { 34 | return fmt.Sprintf("Method %q completed with RequestInfo %v and duration %s", mi.MethodName, mi.RequestInfo, mi.Duration.String()) 35 | } 36 | 37 | type MethodError struct { 38 | RequestInfo *skynet.RequestInfo 39 | MethodName string 40 | Error error 41 | } 42 | 43 | func (me MethodError) String() string { 44 | return fmt.Sprintf("Method %q failed with RequestInfo %v and error %s", me.MethodName, me.RequestInfo, me.Error.Error()) 45 | } 46 | 47 | type KillSignal struct { 48 | Signal syscall.Signal 49 | } 50 | 51 | func (ks KillSignal) String() string { 52 | return fmt.Sprintf("Got kill signal %q", ks.Signal) 53 | } 54 | 55 | type ServiceListening struct { 56 | ServiceInfo *skynet.ServiceInfo 57 | Addr *skynet.BindAddr 58 | } 59 | 60 | func (sc ServiceListening) String() string { 61 | return fmt.Sprintf("Service %q %q listening on %s in region %q", sc.ServiceInfo.Name, sc.ServiceInfo.Version, sc.Addr, sc.ServiceInfo.Region) 62 | } 63 | 64 | type ServiceRegistered struct { 65 | ServiceInfo *skynet.ServiceInfo 66 | } 67 | 68 | func (sr ServiceRegistered) String() string { 69 | return fmt.Sprintf("Service %q registered", sr.ServiceInfo.Name) 70 | } 71 | 72 | type ServiceUnregistered struct { 73 | ServiceInfo *skynet.ServiceInfo 74 | } 75 | 76 | func (sr ServiceUnregistered) String() string { 77 | return fmt.Sprintf("Service %q unregistered", sr.ServiceInfo.Name) 78 | } 79 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/config" 6 | "github.com/skynetservices/skynet/daemon" 7 | "github.com/skynetservices/skynet/log" 8 | "github.com/skynetservices/skynet/rpc/bsonrpc" 9 | "io" 10 | "net" 11 | "net/rpc" 12 | "os" 13 | "os/signal" 14 | "reflect" 15 | "strings" 16 | "sync" 17 | "syscall" 18 | ) 19 | 20 | // A Generic struct to represent any service in the SkyNet system. 21 | type ServiceDelegate interface { 22 | Started(s *Service) 23 | Stopped(s *Service) 24 | Registered(s *Service) 25 | Unregistered(s *Service) 26 | } 27 | 28 | type ClientInfo struct { 29 | Address net.Addr 30 | } 31 | 32 | type Service struct { 33 | *skynet.ServiceInfo 34 | Delegate ServiceDelegate 35 | methods map[string]reflect.Value 36 | RPCServ *rpc.Server 37 | rpcListener *net.TCPListener 38 | activeRequests sync.WaitGroup 39 | connectionChan chan *net.TCPConn 40 | registeredChan chan bool 41 | shutdownChan chan bool 42 | 43 | clientMutex sync.Mutex 44 | ClientInfo map[string]ClientInfo 45 | 46 | // for sending the signal into mux() 47 | doneChan chan bool 48 | 49 | // for waiting for all shutdown operations 50 | doneGroup *sync.WaitGroup 51 | 52 | shuttingDown bool 53 | pipe *daemon.Pipe 54 | } 55 | 56 | // Wraps your custom service in Skynet 57 | func CreateService(sd ServiceDelegate, si *skynet.ServiceInfo) (s *Service) { 58 | s = &Service{ 59 | Delegate: sd, 60 | ServiceInfo: si, 61 | methods: make(map[string]reflect.Value), 62 | connectionChan: make(chan *net.TCPConn), 63 | registeredChan: make(chan bool), 64 | shutdownChan: make(chan bool), 65 | ClientInfo: make(map[string]ClientInfo), 66 | shuttingDown: false, 67 | } 68 | 69 | // Override LogLevel for Service 70 | if l, err := config.String(s.Name, s.Version, "log.level"); err != nil { 71 | log.SetLogLevel(log.LevelFromString(l)) 72 | } 73 | 74 | // I hope I can just rip all this out 75 | /* logWriter := log.NewMultiWriter() 76 | 77 | if logStdout, err := config.Bool(s.Name, s.Version, "service.log.stdout"); err == nil { 78 | if logStdout { 79 | logWriter.AddWriter(os.Stdout) 80 | } 81 | } else { 82 | // Stdout is enabled by default 83 | logWriter.AddWriter(os.Stdout) 84 | } 85 | 86 | if logFile, err := config.String(s.Name, s.Version, "service.log.file"); err == nil { 87 | f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) 88 | 89 | if err != nil { 90 | log.Println(log.PANIC,"Failed to open log file: ", logFile, err) 91 | } 92 | 93 | logWriter.AddWriter(f) 94 | } 95 | 96 | log.SetOutput(logWriter) 97 | */ 98 | 99 | // the main rpc server 100 | s.RPCServ = rpc.NewServer() 101 | rpcForwarder := NewServiceRPC(s) 102 | s.RPCServ.RegisterName(si.Name, rpcForwarder) 103 | 104 | // Daemon doesn't accept commands over pipe 105 | if si.Name != "SkynetDaemon" { 106 | // Listen for admin requests 107 | go s.serveAdminRequests() 108 | } 109 | 110 | return 111 | } 112 | 113 | // Notifies the cluster your service is ready to handle requests 114 | func (s *Service) Register() { 115 | s.registeredChan <- true 116 | } 117 | 118 | func (s *Service) register() { 119 | // this version must be run from the mux() goroutine 120 | if s.Registered { 121 | return 122 | } 123 | 124 | err := skynet.GetServiceManager().Register(s.ServiceInfo.UUID) 125 | if err != nil { 126 | log.Println(log.ERROR, "Failed to register service: "+err.Error()) 127 | } 128 | 129 | s.Registered = true 130 | log.Printf(log.INFO, "%+v\n", ServiceRegistered{s.ServiceInfo}) 131 | s.Delegate.Registered(s) // Call user defined callback 132 | } 133 | 134 | // Leave your service online, but notify the cluster it's not currently accepting new requests 135 | func (s *Service) Unregister() { 136 | s.registeredChan <- false 137 | } 138 | 139 | func (s *Service) unregister() { 140 | // this version must be run from the mux() goroutine 141 | if !s.Registered { 142 | return 143 | } 144 | 145 | err := skynet.GetServiceManager().Unregister(s.UUID) 146 | if err != nil { 147 | log.Println(log.ERROR, "Failed to unregister service: "+err.Error()) 148 | } 149 | 150 | s.Registered = false 151 | log.Printf(log.INFO, "%+v\n", ServiceUnregistered{s.ServiceInfo}) 152 | s.Delegate.Unregistered(s) // Call user defined callback 153 | } 154 | 155 | func (s *Service) Shutdown() { 156 | if s.shuttingDown { 157 | return 158 | } 159 | 160 | s.registeredChan <- false 161 | s.shutdownChan <- true 162 | } 163 | 164 | // Wait for existing requests to complete and shutdown service 165 | func (s *Service) shutdown() { 166 | if s.shuttingDown { 167 | return 168 | } 169 | 170 | s.shuttingDown = true 171 | 172 | s.doneGroup.Add(1) 173 | s.rpcListener.Close() 174 | 175 | s.doneChan <- true 176 | 177 | s.activeRequests.Wait() 178 | 179 | err := skynet.GetServiceManager().Remove(*s.ServiceInfo) 180 | if err != nil { 181 | log.Println(log.ERROR, "Failed to remove service: "+err.Error()) 182 | } 183 | 184 | skynet.GetServiceManager().Shutdown() 185 | 186 | s.Delegate.Stopped(s) // Call user defined callback 187 | 188 | s.doneGroup.Done() 189 | } 190 | 191 | // TODO: Currently unimplemented 192 | func (s *Service) IsTrusted(addr net.Addr) bool { 193 | return false 194 | } 195 | 196 | // Starts your skynet service, including binding to ports. Optionally register for requests at the same time. Returns a sync.WaitGroup that will block until all requests have finished 197 | func (s *Service) Start() (done *sync.WaitGroup) { 198 | bindWait := &sync.WaitGroup{} 199 | 200 | bindWait.Add(1) 201 | go s.listen(s.ServiceAddr, bindWait) 202 | 203 | // Watch signals for shutdown 204 | c := make(chan os.Signal, 1) 205 | go watchSignals(c, s) 206 | 207 | s.doneChan = make(chan bool, 1) 208 | 209 | // We must block here, we don't want to register, until we've actually bound to an ip:port 210 | bindWait.Wait() 211 | 212 | s.doneGroup = &sync.WaitGroup{} 213 | s.doneGroup.Add(1) 214 | 215 | go func() { 216 | s.mux() 217 | s.doneGroup.Done() 218 | }() 219 | done = s.doneGroup 220 | 221 | if r, err := config.Bool(s.Name, s.Version, "service.register"); err == nil { 222 | s.Registered = r 223 | } 224 | 225 | err := skynet.GetServiceManager().Add(*s.ServiceInfo) 226 | if err != nil { 227 | log.Println(log.ERROR, "Failed to add service: "+err.Error()) 228 | } 229 | 230 | if s.Registered { 231 | s.Register() 232 | } 233 | 234 | go s.Delegate.Started(s) // Call user defined callback 235 | 236 | if s.ServiceInfo.Registered { 237 | go s.Delegate.Registered(s) // Call user defined callback 238 | } 239 | 240 | return 241 | } 242 | 243 | func (s *Service) getClientInfo(clientID string) (ci ClientInfo, ok bool) { 244 | s.clientMutex.Lock() 245 | defer s.clientMutex.Unlock() 246 | 247 | ci, ok = s.ClientInfo[clientID] 248 | return 249 | } 250 | 251 | func (s *Service) listen(addr skynet.BindAddr, bindWait *sync.WaitGroup) { 252 | var err error 253 | s.rpcListener, err = addr.Listen() 254 | if err != nil { 255 | log.Fatal(err) 256 | } 257 | 258 | log.Printf(log.INFO, "%+v\n", ServiceListening{ 259 | Addr: &addr, 260 | ServiceInfo: s.ServiceInfo, 261 | }) 262 | 263 | // We may have changed port due to conflict, ensure config has the correct port now 264 | a, _ := skynet.BindAddrFromString(addr.String()) 265 | s.ServiceAddr.IPAddress = a.IPAddress 266 | s.ServiceAddr.Port = a.Port 267 | 268 | bindWait.Done() 269 | 270 | for { 271 | conn, err := s.rpcListener.AcceptTCP() 272 | 273 | if s.shuttingDown { 274 | break 275 | } 276 | 277 | if err != nil && !s.shuttingDown { 278 | log.Println(log.ERROR, "AcceptTCP failed", err) 279 | continue 280 | } 281 | s.connectionChan <- conn 282 | } 283 | } 284 | 285 | // this function is the goroutine that owns this service - all thread-sensitive data needs to 286 | // be manipulated only through here. 287 | func (s *Service) mux() { 288 | loop: 289 | for { 290 | select { 291 | case conn := <-s.connectionChan: 292 | go func() { 293 | clientID := config.NewUUID() 294 | 295 | s.clientMutex.Lock() 296 | s.ClientInfo[clientID] = ClientInfo{ 297 | Address: conn.RemoteAddr(), 298 | } 299 | s.clientMutex.Unlock() 300 | 301 | // send the server handshake 302 | sh := skynet.ServiceHandshake{ 303 | Registered: s.Registered, 304 | ClientID: clientID, 305 | Name: s.Name, 306 | } 307 | 308 | codec := bsonrpc.NewServerCodec(conn) 309 | 310 | log.Println(log.TRACE, "Sending ServiceHandshake") 311 | err := codec.Encoder.Encode(sh) 312 | if err != nil { 313 | log.Println(log.ERROR, "Failed to encode server handshake", err.Error()) 314 | conn.Close() 315 | return 316 | } 317 | if !s.Registered { 318 | log.Println(log.ERROR, "Connection attempted while unregistered. Closing connection") 319 | conn.Close() 320 | return 321 | } 322 | 323 | // read the client handshake 324 | var ch skynet.ClientHandshake 325 | log.Println(log.TRACE, "Reading ClientHandshake") 326 | err = codec.Decoder.Decode(&ch) 327 | if err != nil { 328 | log.Println(log.ERROR, "Error decoding ClientHandshake: "+err.Error()) 329 | conn.Close() 330 | return 331 | } 332 | 333 | // here do stuff with the client handshake 334 | log.Println(log.TRACE, "Handing connection to RPC layer") 335 | s.RPCServ.ServeCodec(codec) 336 | }() 337 | case register := <-s.registeredChan: 338 | if register { 339 | s.register() 340 | } else { 341 | s.unregister() 342 | } 343 | case <-s.shutdownChan: 344 | s.shutdown() 345 | case _ = <-s.doneChan: 346 | break loop 347 | } 348 | } 349 | } 350 | 351 | func (s *Service) serveAdminRequests() { 352 | rId := os.Stderr.Fd() + 2 353 | wId := os.Stderr.Fd() + 3 354 | 355 | pipeReader := os.NewFile(uintptr(rId), "") 356 | pipeWriter := os.NewFile(uintptr(wId), "") 357 | s.pipe = daemon.NewPipe(pipeReader, pipeWriter) 358 | 359 | b := make([]byte, daemon.MAX_PIPE_BYTES) 360 | for { 361 | n, err := s.pipe.Read(b) 362 | 363 | if err != nil { 364 | if err != io.EOF { 365 | log.Printf(log.ERROR, "Error reading from admin pipe "+err.Error()) 366 | } else { 367 | // We received EOF, ensure we shutdown (if daemon died we could be orphaned) 368 | s.Shutdown() 369 | } 370 | 371 | return 372 | } 373 | 374 | cmd := string(b[:n]) 375 | log.Println(log.TRACE, "Received "+cmd+" from daemon") 376 | 377 | switch cmd { 378 | case "SHUTDOWN": 379 | s.Shutdown() 380 | s.pipe.Write([]byte("ACK")) 381 | break 382 | case "REGISTER": 383 | s.Register() 384 | s.pipe.Write([]byte("ACK")) 385 | case "UNREGISTER": 386 | s.Unregister() 387 | s.pipe.Write([]byte("ACK")) 388 | case "LOG DEBUG", "LOG TRACE", "LOG INFO", "LOG WARN", "LOG ERROR", "LOG FATAL", "LOG PANIC": 389 | parts := strings.Split(cmd, " ") 390 | log.SetLogLevel(log.LevelFromString(parts[1])) 391 | log.Println(log.INFO, "Setting log level to "+parts[1]) 392 | 393 | s.pipe.Write([]byte("ACK")) 394 | } 395 | } 396 | } 397 | 398 | func watchSignals(c chan os.Signal, s *Service) { 399 | signal.Notify(c, syscall.SIGINT, syscall.SIGKILL, syscall.SIGSEGV, syscall.SIGSTOP, syscall.SIGTERM) 400 | 401 | for { 402 | select { 403 | case sig := <-c: 404 | switch sig.(syscall.Signal) { 405 | // Trap signals for clean shutdown 406 | case syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT, 407 | syscall.SIGSEGV, syscall.SIGSTOP, syscall.SIGTERM: 408 | log.Printf(log.INFO, "%+v", KillSignal{sig.(syscall.Signal)}) 409 | s.Shutdown() 410 | return 411 | } 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /service/servicerpc.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/skynetservices/skynet" 7 | "github.com/skynetservices/skynet/log" 8 | "github.com/skynetservices/skynet/stats" 9 | "labix.org/v2/mgo/bson" 10 | "reflect" 11 | "time" 12 | ) 13 | 14 | var ( 15 | RequestInfoPtrType = reflect.TypeOf(&skynet.RequestInfo{}) 16 | 17 | anError error 18 | ErrorType = reflect.TypeOf(&anError).Elem() 19 | ) 20 | 21 | type ServiceRPC struct { 22 | service *Service 23 | methods map[string]reflect.Value 24 | MethodNames []string 25 | } 26 | 27 | var reservedMethodNames = map[string]bool{} 28 | 29 | func init() { 30 | 31 | var sd ServiceDelegate 32 | sdvalue := reflect.ValueOf(&sd).Elem().Type() 33 | for i := 0; i < sdvalue.NumMethod(); i++ { 34 | m := sdvalue.Method(i) 35 | reservedMethodNames[m.Name] = true 36 | } 37 | } 38 | 39 | func NewServiceRPC(s *Service) (srpc *ServiceRPC) { 40 | srpc = &ServiceRPC{ 41 | service: s, 42 | methods: make(map[string]reflect.Value), 43 | } 44 | 45 | // scan through methods looking for a method (RequestInfo, 46 | // something, something) error 47 | typ := reflect.TypeOf(srpc.service.Delegate) 48 | for i := 0; i < typ.NumMethod(); i++ { 49 | m := typ.Method(i) 50 | 51 | if reservedMethodNames[m.Name] { 52 | continue 53 | } 54 | 55 | // this is the check to see if something is exported 56 | if m.PkgPath != "" { 57 | continue 58 | } 59 | 60 | f := m.Func 61 | ftyp := f.Type() 62 | 63 | // must have four parameters: (receiver, RequestInfo, 64 | // somethingIn, somethingOut) 65 | if ftyp.NumIn() != 4 { 66 | goto problem 67 | } 68 | 69 | // don't have to check for the receiver 70 | 71 | // check the second parameter 72 | if ftyp.In(1) != RequestInfoPtrType { 73 | goto problem 74 | } 75 | 76 | // the somethingIn can be anything 77 | 78 | // somethingOut must be a pointer or a map 79 | switch ftyp.In(3).Kind() { 80 | case reflect.Ptr, reflect.Map: 81 | default: 82 | goto problem 83 | } 84 | 85 | // must have one return value that is an error 86 | if ftyp.NumOut() != 1 { 87 | goto problem 88 | } 89 | if ftyp.Out(0) != ErrorType { 90 | goto problem 91 | } 92 | 93 | // we've got a method! 94 | srpc.methods[m.Name] = f 95 | srpc.MethodNames = append(srpc.MethodNames, m.Name) 96 | continue 97 | 98 | problem: 99 | log.Printf(log.WARN, "Bad RPC method for %T: %q %v\n", s.Delegate, m.Name, f) 100 | } 101 | 102 | return 103 | } 104 | 105 | // ServiceRPC.Forward is the entry point for RPC calls. It wraps actual RPC calls 106 | // and provides a slot for the RequestInfo. The parameters to the actual RPC 107 | // calls are transmitted in a []byte, and are then marshalled/unmarshalled on 108 | // either end. 109 | func (srpc *ServiceRPC) Forward(in skynet.ServiceRPCInRead, out *skynet.ServiceRPCOutWrite) (err error) { 110 | srpc.service.activeRequests.Add(1) 111 | defer srpc.service.activeRequests.Done() 112 | 113 | go stats.MethodCalled(in.Method) 114 | 115 | clientInfo, ok := srpc.service.getClientInfo(in.ClientID) 116 | if !ok { 117 | err = errors.New("did not provide the ClientID") 118 | log.Printf(log.ERROR, "%+v", MethodError{in.RequestInfo, in.Method, err}) 119 | return 120 | } 121 | 122 | in.RequestInfo.ConnectionAddress = clientInfo.Address.String() 123 | if in.RequestInfo.OriginAddress == "" || !srpc.service.IsTrusted(clientInfo.Address) { 124 | in.RequestInfo.OriginAddress = in.RequestInfo.ConnectionAddress 125 | } 126 | 127 | mc := MethodCall{ 128 | MethodName: in.Method, 129 | RequestInfo: in.RequestInfo, 130 | } 131 | 132 | log.Printf(log.INFO, "%+v", mc) 133 | 134 | m, ok := srpc.methods[in.Method] 135 | if !ok { 136 | err = errors.New(fmt.Sprintf("No such method %q", in.Method)) 137 | log.Printf(log.ERROR, "%+v", MethodError{in.RequestInfo, in.Method, err}) 138 | return 139 | } 140 | 141 | inValuePtr := reflect.New(m.Type().In(2)) 142 | 143 | err = bson.Unmarshal(in.In, inValuePtr.Interface()) 144 | if err != nil { 145 | log.Println(log.ERROR, "Error unmarshaling request ", err) 146 | return 147 | } 148 | 149 | // Allocate the out parameter of the RPC call. 150 | outType := m.Type().In(3) 151 | var outValue reflect.Value 152 | 153 | switch outType.Kind() { 154 | case reflect.Ptr: 155 | outValue = reflect.New(m.Type().In(3).Elem()) 156 | case reflect.Map: 157 | outValue = reflect.MakeMap(outType) 158 | default: 159 | err = errors.New("illegal out param type") 160 | log.Printf(log.ERROR, "%+v", MethodError{in.RequestInfo, in.Method, err}) 161 | return 162 | } 163 | 164 | startTime := time.Now() 165 | 166 | params := []reflect.Value{ 167 | reflect.ValueOf(srpc.service.Delegate), 168 | reflect.ValueOf(in.RequestInfo), 169 | inValuePtr.Elem(), 170 | outValue, 171 | } 172 | 173 | returns := m.Call(params) 174 | 175 | duration := time.Now().Sub(startTime) 176 | 177 | mcp := MethodCompletion{ 178 | MethodName: in.Method, 179 | RequestInfo: in.RequestInfo, 180 | Duration: duration, 181 | } 182 | 183 | log.Printf(log.INFO, "%+v", mcp) 184 | 185 | var b []byte 186 | b, err = bson.Marshal(outValue.Interface()) 187 | if err != nil { 188 | log.Printf(log.ERROR, "%+v", MethodError{in.RequestInfo, in.Method, fmt.Errorf("Error marshaling response", err)}) 189 | return 190 | } 191 | 192 | out.Out = bson.Binary{ 193 | 0x00, 194 | b, 195 | } 196 | 197 | var rerr error = nil 198 | erri := returns[0].Interface() 199 | if erri != nil { 200 | rerr, _ = erri.(error) 201 | out.ErrString = rerr.Error() 202 | 203 | log.Printf(log.ERROR, "%+v", MethodError{in.RequestInfo, in.Method, fmt.Errorf("Method returned error:", err)}) 204 | } 205 | 206 | go stats.MethodCompleted(in.Method, duration, rerr) 207 | 208 | return 209 | } 210 | -------------------------------------------------------------------------------- /service/servicerpc_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "github.com/skynetservices/skynet" 6 | "labix.org/v2/mgo/bson" 7 | "net" 8 | "testing" 9 | ) 10 | 11 | type M map[string]interface{} 12 | 13 | type EchoRPC struct { 14 | } 15 | 16 | func (e EchoRPC) Started(s *Service) {} 17 | func (e EchoRPC) Stopped(s *Service) {} 18 | func (e EchoRPC) Registered(s *Service) {} 19 | func (e EchoRPC) Unregistered(s *Service) {} 20 | 21 | func (e EchoRPC) Foo(rinfo *skynet.RequestInfo, in M, out *M) (err error) { 22 | *out = M{ 23 | "Hi": in["Hi"], 24 | } 25 | 26 | return 27 | } 28 | 29 | func TestServiceRPCBasic(t *testing.T) { 30 | var addr net.Addr 31 | 32 | config := &skynet.ServiceConfig{Name: "EchoRPC"} 33 | service := CreateService(EchoRPC{}, config) 34 | service.ClientInfo = make(map[string]ClientInfo, 1) 35 | 36 | addr = &net.TCPAddr{ 37 | IP: net.ParseIP("127.0.0.1"), 38 | Port: 123, 39 | } 40 | 41 | service.ClientInfo["123"] = ClientInfo{ 42 | Address: addr, 43 | } 44 | 45 | srpc := NewServiceRPC(service) 46 | 47 | in := M{"Hi": "there"} 48 | out := &M{} 49 | 50 | sin := skynet.ServiceRPCIn{ 51 | RequestInfo: &skynet.RequestInfo{ 52 | RequestID: "id", 53 | OriginAddress: addr.String(), 54 | ConnectionAddress: addr.String(), 55 | }, 56 | Method: "Foo", 57 | ClientID: "123", 58 | } 59 | 60 | sin.In, _ = bson.Marshal(in) 61 | 62 | sout := skynet.ServiceRPCOut{} 63 | 64 | err := srpc.Forward(sin, &sout) 65 | if err != nil { 66 | t.Error(err) 67 | } 68 | 69 | bson.Unmarshal(sout.Out, out) 70 | 71 | if v, ok := (*out)["Hi"].(string); !ok || v != "there" { 72 | t.Error(fmt.Sprintf("Expected %v, got %v", in, *out)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /serviceinfo.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | import ( 4 | "fmt" 5 | "github.com/skynetservices/skynet/config" 6 | "github.com/skynetservices/skynet/log" 7 | "net" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | var portMutex sync.Mutex 14 | 15 | // ServiceStatistics contains information about its service that can 16 | // be used to estimate load. 17 | type ServiceStatistics struct { 18 | // Clients is the number of clients currently connected to this service. 19 | Clients int32 20 | // StartTime is the time when the service began running. 21 | StartTime string 22 | // LastRequest is the time when the last request was made. 23 | LastRequest string 24 | } 25 | 26 | // ServiceInfo is the publicly reported information about a particular 27 | // service instance. 28 | type ServiceInfo struct { 29 | UUID string 30 | Name string 31 | Version string 32 | Region string 33 | 34 | ServiceAddr BindAddr 35 | 36 | // Registered indicates if the instance is currently accepting requests. 37 | Registered bool 38 | } 39 | 40 | func (si ServiceInfo) AddrString() string { 41 | return si.ServiceAddr.String() 42 | } 43 | 44 | func NewServiceInfo(name, version string) (si *ServiceInfo) { 45 | // TODO: we need to grab Host/Region/ServiceAddr from config 46 | si = &ServiceInfo{ 47 | Name: name, 48 | Version: version, 49 | UUID: config.UUID(), 50 | } 51 | 52 | var host string 53 | var minPort, maxPort int 54 | 55 | if r, err := config.String(name, version, "region"); err == nil { 56 | si.Region = r 57 | } else { 58 | si.Region = config.DefaultRegion 59 | } 60 | 61 | if h, err := config.String(name, version, "host"); err == nil { 62 | host = h 63 | } else { 64 | host = config.DefaultHost 65 | } 66 | 67 | if p, err := config.Int(name, version, "service.port.min"); err == nil { 68 | minPort = p 69 | } else { 70 | minPort = config.DefaultMinPort 71 | } 72 | 73 | if p, err := config.Int(name, version, "service.port.max"); err == nil { 74 | maxPort = p 75 | } else { 76 | maxPort = config.DefaultMaxPort 77 | } 78 | 79 | log.Println(log.TRACE, host, minPort, maxPort) 80 | si.ServiceAddr = BindAddr{IPAddress: host, Port: minPort, MaxPort: maxPort} 81 | 82 | return si 83 | } 84 | 85 | type BindAddr struct { 86 | IPAddress string 87 | Port int 88 | MaxPort int 89 | } 90 | 91 | func BindAddrFromString(host string) (ba BindAddr, err error) { 92 | if host == "" { 93 | return 94 | } 95 | split := strings.Index(host, ":") 96 | if split == -1 { 97 | err = fmt.Errorf("Must specify a port for address (got %q)", host) 98 | return 99 | } 100 | 101 | ba = BindAddr{} 102 | 103 | ba.IPAddress = host[:split] 104 | if ba.IPAddress == "" { 105 | ba.IPAddress = "0.0.0.0" 106 | } 107 | 108 | portstr := host[split+1:] 109 | if ba.Port, err = strconv.Atoi(portstr); err == nil { 110 | return 111 | } 112 | 113 | var rindex int 114 | if rindex = strings.Index(portstr, "-"); rindex == -1 { 115 | err = fmt.Errorf("Couldn't process port for %q: %v", host, err) 116 | return 117 | } 118 | 119 | maxPortStr := portstr[rindex+1:] 120 | portstr = portstr[:rindex] 121 | 122 | if ba.Port, err = strconv.Atoi(portstr); err != nil { 123 | err = fmt.Errorf("Couldn't process port for %q: %v", host, err) 124 | return 125 | } 126 | if ba.MaxPort, err = strconv.Atoi(maxPortStr); err != nil { 127 | err = fmt.Errorf("Couldn't process port for %q: %v", host, err) 128 | return 129 | } 130 | 131 | return 132 | } 133 | 134 | func (ba *BindAddr) String() string { 135 | if ba == nil { 136 | return "" 137 | } 138 | return fmt.Sprintf("%s:%d", ba.IPAddress, ba.Port) 139 | } 140 | 141 | func (ba *BindAddr) Listen() (listener *net.TCPListener, err error) { 142 | // Ensure Admin, and RPC don't fight over the same port 143 | portMutex.Lock() 144 | defer portMutex.Unlock() 145 | 146 | for { 147 | var laddr *net.TCPAddr 148 | laddr, err = net.ResolveTCPAddr("tcp", ba.String()) 149 | if err != nil { 150 | panic(err) 151 | } 152 | listener, err = net.ListenTCP("tcp", laddr) 153 | if err == nil { 154 | return 155 | } 156 | if ba.Port < ba.MaxPort { 157 | ba.Port++ 158 | } else { 159 | return 160 | } 161 | 162 | } 163 | return 164 | } 165 | -------------------------------------------------------------------------------- /servicemanager.go: -------------------------------------------------------------------------------- 1 | package skynet 2 | 3 | import ( 4 | "github.com/skynetservices/skynet/log" 5 | ) 6 | 7 | const ( 8 | _ = iota 9 | InstanceAdded 10 | InstanceRemoved 11 | InstanceUpdated 12 | ) 13 | 14 | type InstanceNotification struct { 15 | Type int 16 | Service ServiceInfo 17 | } 18 | 19 | type ServiceManager interface { 20 | Add(s ServiceInfo) error 21 | Update(s ServiceInfo) error 22 | Remove(s ServiceInfo) error 23 | Register(uuid string) error 24 | Unregister(uuid string) error 25 | 26 | Shutdown() error 27 | 28 | // Discovery 29 | ListHosts(c CriteriaMatcher) ([]string, error) 30 | ListRegions(c CriteriaMatcher) ([]string, error) 31 | ListServices(c CriteriaMatcher) ([]string, error) 32 | ListVersions(c CriteriaMatcher) ([]string, error) 33 | ListInstances(c CriteriaMatcher) ([]ServiceInfo, error) 34 | Watch(criteria CriteriaMatcher, c chan<- InstanceNotification) []ServiceInfo 35 | } 36 | 37 | var manager ServiceManager 38 | 39 | func SetServiceManager(sm ServiceManager) { 40 | manager = sm 41 | } 42 | 43 | func GetServiceManager() ServiceManager { 44 | if manager == nil { 45 | log.Fatal("No ServiceManager provided") 46 | } 47 | 48 | return manager 49 | } 50 | -------------------------------------------------------------------------------- /stats/hoststats.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "github.com/jondot/gosigar" 5 | ) 6 | 7 | type Host struct { 8 | Uptime sigar.Uptime 9 | LoadAverage sigar.LoadAverage 10 | Mem sigar.Mem 11 | Swap sigar.Swap 12 | Cpu sigar.Cpu 13 | } 14 | 15 | func (h *Host) Update(name string) { 16 | h.Uptime.Get() 17 | h.LoadAverage.Get() 18 | h.Mem.Get() 19 | h.Swap.Get() 20 | h.Cpu.Get() 21 | 22 | UpdateHostStats(name, *h) 23 | } 24 | -------------------------------------------------------------------------------- /stats/reporter.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var reporters []Reporter 8 | 9 | type Reporter interface { 10 | UpdateHostStats(host string, stats Host) 11 | MethodCalled(method string) 12 | MethodCompleted(method string, duration time.Duration, err error) 13 | } 14 | 15 | func AddReporter(r Reporter) { 16 | reporters = append(reporters, r) 17 | } 18 | 19 | func UpdateHostStats(host string, s Host) { 20 | for _, r := range reporters { 21 | go r.UpdateHostStats(host, s) 22 | } 23 | } 24 | 25 | func MethodCalled(method string) { 26 | for _, r := range reporters { 27 | go r.MethodCalled(method) 28 | } 29 | } 30 | 31 | func MethodCompleted(method string, duration time.Duration, err error) { 32 | for _, r := range reporters { 33 | go r.MethodCompleted(method, duration, err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/connection.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "time" 6 | ) 7 | 8 | type Connection struct { 9 | SetIdleTimeoutFunc func(timeout time.Duration) 10 | AddrFunc func() string 11 | 12 | CloseFunc func() 13 | IsClosedFunc func() bool 14 | 15 | SendFunc func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 16 | SendTimeoutFunc func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}, timeout time.Duration) (err error) 17 | } 18 | 19 | func (c *Connection) SetIdleTimeout(timeout time.Duration) { 20 | if c.SetIdleTimeoutFunc != nil { 21 | c.SetIdleTimeoutFunc(timeout) 22 | } 23 | } 24 | 25 | func (c *Connection) Addr() string { 26 | if c.AddrFunc != nil { 27 | return c.AddrFunc() 28 | } 29 | 30 | return "" 31 | } 32 | 33 | func (c *Connection) Close() { 34 | if c.CloseFunc != nil { 35 | c.CloseFunc() 36 | } 37 | } 38 | 39 | func (c *Connection) IsClosed() bool { 40 | if c.IsClosedFunc != nil { 41 | return c.IsClosedFunc() 42 | } 43 | 44 | return false 45 | } 46 | 47 | func (c *Connection) Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 48 | if c.SendFunc != nil { 49 | return c.SendFunc(ri, fn, in, out) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (c *Connection) SendTimeout(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}, timeout time.Duration) (err error) { 56 | if c.SendTimeoutFunc != nil { 57 | return c.SendTimeoutFunc(ri, fn, in, out, timeout) 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /test/loadbalancer.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "github.com/skynetservices/skynet" 6 | "github.com/skynetservices/skynet/client/loadbalancer" 7 | ) 8 | 9 | type LoadBalancer struct { 10 | AddInstanceFunc func(s skynet.ServiceInfo) 11 | UpdateInstanceFunc func(s skynet.ServiceInfo) 12 | RemoveInstanceFunc func(s skynet.ServiceInfo) 13 | ChooseFunc func() (skynet.ServiceInfo, error) 14 | } 15 | 16 | func NewLoadBalancer(instances []skynet.ServiceInfo) (lb loadbalancer.LoadBalancer) { 17 | return &LoadBalancer{} 18 | } 19 | 20 | func (lb *LoadBalancer) AddInstance(s skynet.ServiceInfo) { 21 | if lb.AddInstanceFunc != nil { 22 | lb.AddInstanceFunc(s) 23 | } 24 | } 25 | 26 | func (lb *LoadBalancer) UpdateInstance(s skynet.ServiceInfo) { 27 | if lb.UpdateInstanceFunc != nil { 28 | lb.UpdateInstanceFunc(s) 29 | } 30 | } 31 | 32 | func (lb *LoadBalancer) RemoveInstance(s skynet.ServiceInfo) { 33 | if lb.RemoveInstanceFunc != nil { 34 | lb.RemoveInstanceFunc(s) 35 | } 36 | } 37 | 38 | func (lb *LoadBalancer) Choose() (skynet.ServiceInfo, error) { 39 | if lb.ChooseFunc != nil { 40 | return lb.ChooseFunc() 41 | } 42 | 43 | return skynet.ServiceInfo{}, errors.New("No instances found that match that criteria") 44 | } 45 | -------------------------------------------------------------------------------- /test/pool.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "github.com/skynetservices/skynet/client/conn" 6 | ) 7 | 8 | type Pool struct { 9 | AddInstanceFunc func(s skynet.ServiceInfo) 10 | UpdateInstanceFunc func(s skynet.ServiceInfo) 11 | RemoveInstanceFunc func(s skynet.ServiceInfo) 12 | 13 | AcquireFunc func(s skynet.ServiceInfo) (conn.Connection, error) 14 | ReleaseFunc func(conn.Connection) 15 | 16 | CloseFunc func() 17 | NumInstancesFunc func() int 18 | NumConnectionsFunc func() int 19 | } 20 | 21 | func (p *Pool) AddInstance(s skynet.ServiceInfo) { 22 | if p.AddInstanceFunc != nil { 23 | p.AddInstanceFunc(s) 24 | } 25 | } 26 | 27 | func (p *Pool) UpdateInstance(s skynet.ServiceInfo) { 28 | if p.UpdateInstanceFunc != nil { 29 | p.UpdateInstanceFunc(s) 30 | } 31 | } 32 | 33 | func (p *Pool) RemoveInstance(s skynet.ServiceInfo) { 34 | if p.RemoveInstanceFunc != nil { 35 | p.RemoveInstanceFunc(s) 36 | } 37 | } 38 | 39 | func (p *Pool) Acquire(s skynet.ServiceInfo) (conn.Connection, error) { 40 | if p.AcquireFunc != nil { 41 | return p.AcquireFunc(s) 42 | } 43 | 44 | return nil, nil 45 | } 46 | 47 | func (p *Pool) Release(c conn.Connection) { 48 | if p.ReleaseFunc != nil { 49 | p.ReleaseFunc(c) 50 | } 51 | } 52 | 53 | func (p *Pool) Close() { 54 | if p.CloseFunc != nil { 55 | p.CloseFunc() 56 | } 57 | } 58 | 59 | func (p *Pool) NumInstances() int { 60 | if p.NumInstancesFunc != nil { 61 | return p.NumInstancesFunc() 62 | } 63 | 64 | return 0 65 | } 66 | 67 | func (p *Pool) NumConnections() int { 68 | if p.NumConnectionsFunc != nil { 69 | return p.NumConnectionsFunc() 70 | } 71 | 72 | return 0 73 | } 74 | -------------------------------------------------------------------------------- /test/serviceclient.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | "time" 6 | ) 7 | 8 | type ServiceClient struct { 9 | SetDefaultTimeoutFunc func(retry, giveup time.Duration) 10 | GetDefaultTimeoutFunc func() (retry, giveup time.Duration) 11 | 12 | CloseFunc func() 13 | 14 | SendFunc func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 15 | SendOnceFunc func(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) 16 | 17 | NotifyFunc func(n skynet.InstanceNotification) 18 | MatchesFunc func(n skynet.ServiceInfo) bool 19 | } 20 | 21 | func (sc *ServiceClient) SetDefaultTimeout(retry, giveup time.Duration) { 22 | if sc.SetDefaultTimeoutFunc != nil { 23 | sc.SetDefaultTimeoutFunc(retry, giveup) 24 | } 25 | 26 | return 27 | } 28 | 29 | func (sc *ServiceClient) GetDefaultTimeout() (retry, giveup time.Duration) { 30 | if sc.GetDefaultTimeoutFunc != nil { 31 | return sc.GetDefaultTimeoutFunc() 32 | } 33 | 34 | return 35 | } 36 | 37 | func (sc *ServiceClient) Send(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 38 | if sc.SendFunc != nil { 39 | return sc.SendFunc(ri, fn, in, out) 40 | } 41 | 42 | return 43 | } 44 | 45 | func (sc *ServiceClient) SendOnce(ri *skynet.RequestInfo, fn string, in interface{}, out interface{}) (err error) { 46 | if sc.SendOnceFunc != nil { 47 | return sc.SendOnceFunc(ri, fn, in, out) 48 | } 49 | 50 | return 51 | } 52 | 53 | func (sc *ServiceClient) Close() { 54 | if sc.CloseFunc != nil { 55 | sc.CloseFunc() 56 | } 57 | 58 | return 59 | } 60 | 61 | func (sc *ServiceClient) Notify(n skynet.InstanceNotification) { 62 | if sc.NotifyFunc != nil { 63 | sc.NotifyFunc(n) 64 | } 65 | 66 | return 67 | } 68 | 69 | func (sc *ServiceClient) Matches(s skynet.ServiceInfo) bool { 70 | if sc.MatchesFunc != nil { 71 | return sc.MatchesFunc(s) 72 | } 73 | 74 | return false 75 | } 76 | -------------------------------------------------------------------------------- /test/servicemanager.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/skynetservices/skynet" 5 | ) 6 | 7 | type ServiceManager struct { 8 | AddFunc func(s skynet.ServiceInfo) error 9 | UpdateFunc func(s skynet.ServiceInfo) error 10 | RemoveFunc func(s skynet.ServiceInfo) error 11 | RegisterFunc func(uuid string) error 12 | UnregisterFunc func(uuid string) error 13 | 14 | ShutdownFunc func() error 15 | 16 | ListHostsFunc func(c skynet.CriteriaMatcher) ([]string, error) 17 | ListRegionsFunc func(c skynet.CriteriaMatcher) ([]string, error) 18 | ListServicesFunc func(c skynet.CriteriaMatcher) ([]string, error) 19 | ListVersionsFunc func(c skynet.CriteriaMatcher) ([]string, error) 20 | ListInstancesFunc func(c skynet.CriteriaMatcher) ([]skynet.ServiceInfo, error) 21 | WatchFunc func(criteria skynet.CriteriaMatcher, c chan<- skynet.InstanceNotification) []skynet.ServiceInfo 22 | } 23 | 24 | func (sm *ServiceManager) Add(s skynet.ServiceInfo) error { 25 | if sm.AddFunc != nil { 26 | return sm.AddFunc(s) 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (sm *ServiceManager) Update(s skynet.ServiceInfo) error { 33 | if sm.UpdateFunc != nil { 34 | return sm.UpdateFunc(s) 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (sm *ServiceManager) Remove(s skynet.ServiceInfo) error { 41 | if sm.RemoveFunc != nil { 42 | return sm.RemoveFunc(s) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (sm *ServiceManager) Register(uuid string) error { 49 | if sm.RegisterFunc != nil { 50 | return sm.RegisterFunc(uuid) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func (sm *ServiceManager) Unregister(uuid string) error { 57 | if sm.UnregisterFunc != nil { 58 | return sm.UnregisterFunc(uuid) 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func (sm *ServiceManager) Shutdown() error { 65 | if sm.ShutdownFunc != nil { 66 | return sm.ShutdownFunc() 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func (sm *ServiceManager) ListHosts(c skynet.CriteriaMatcher) ([]string, error) { 73 | if sm.ListHostsFunc != nil { 74 | return sm.ListHostsFunc(c) 75 | } 76 | 77 | return []string{}, nil 78 | } 79 | 80 | func (sm *ServiceManager) ListRegions(c skynet.CriteriaMatcher) ([]string, error) { 81 | if sm.ListRegionsFunc != nil { 82 | return sm.ListRegionsFunc(c) 83 | } 84 | 85 | return []string{}, nil 86 | } 87 | 88 | func (sm *ServiceManager) ListServices(c skynet.CriteriaMatcher) ([]string, error) { 89 | if sm.ListServicesFunc != nil { 90 | return sm.ListServicesFunc(c) 91 | } 92 | 93 | return []string{}, nil 94 | } 95 | 96 | func (sm *ServiceManager) ListVersions(c skynet.CriteriaMatcher) ([]string, error) { 97 | if sm.ListVersionsFunc != nil { 98 | return sm.ListVersionsFunc(c) 99 | } 100 | 101 | return []string{}, nil 102 | } 103 | 104 | func (sm *ServiceManager) ListInstances(c skynet.CriteriaMatcher) ([]skynet.ServiceInfo, error) { 105 | if sm.ListInstancesFunc != nil { 106 | return sm.ListInstancesFunc(c) 107 | } 108 | 109 | return []skynet.ServiceInfo{}, nil 110 | } 111 | 112 | func (sm *ServiceManager) Watch(criteria skynet.CriteriaMatcher, c chan<- skynet.InstanceNotification) (s []skynet.ServiceInfo) { 113 | if sm.ListInstancesFunc != nil { 114 | return sm.WatchFunc(criteria, c) 115 | } 116 | 117 | return 118 | } 119 | -------------------------------------------------------------------------------- /tools/conf/skynet.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | zookeeper.addr = zookeeper:2181 3 | zookeeper.timeout = 1s 4 | 5 | host = 10.10.5.5 6 | region = "Development" 7 | 8 | log.level = DEBUG 9 | log.sysloghost = "" 10 | log.syslogport = 514 11 | 12 | client.conn.max = 5 13 | client.conn.idle = 2 14 | 15 | client.timeout.total = 10s 16 | client.timeout.retry = 2s 17 | client.timeout.idle = 5s 18 | 19 | service.port.min = 9000 20 | service.port.max = 9999 21 | 22 | # Override values at the service level 23 | [TestService] 24 | service.port.min = 8000 25 | service.port.max = 8999 26 | -------------------------------------------------------------------------------- /tools/upstart/skydaemon.conf: -------------------------------------------------------------------------------- 1 | description "Skynet Daemon" 2 | 3 | start on (local-filesystems and net-device-up IFACE!=lo) 4 | 5 | kill signal TERM 6 | kill timeout 60 7 | 8 | respawn 9 | respawn limit 10 5 10 | 11 | setgid root 12 | setuid root 13 | 14 | # oom score -999 15 | #console log 16 | 17 | script 18 | . /etc/environment 19 | 20 | export SKYNET_BIND_IP 21 | export SKYNET_HOST 22 | export SKYNET_MIN_PORT 23 | export SKYNET_MAX_PORT 24 | export SKYNET_REGION 25 | export SKYNET_ZOOKEEPER 26 | export SKYNET_SERVICE_DIR 27 | 28 | $SKYNET_SERVICE_DIR/skydaemon 29 | end script 30 | --------------------------------------------------------------------------------