1 //
2 // Copyright (C) 2014-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 /*
27 Package client provides a SysDB client implementation.
29 The Connect function connects to a SysDB server as the specified user:
31 c, err := client.Connect("unix:/var/run/sysdbd.sock", "username")
32 if err != nil {
33 // handle error
34 }
35 defer c.Close()
37 Then, it can issue requests to the server:
39 major, minor, patch, extra, err := c.ServerVersion()
40 if err != nil {
41 // handle error
42 }
43 fmt.Printf("Connected to SysDB %d.%d.%d%s\n", major, minor, patch, extra)
45 or:
47 res, err := c.Call(&proto.Message{Type: proto.ConnectionServerVersion})
48 if err != nil {
49 // handle error
50 }
51 fmt.Printf("%v\n", res)
53 Each client maintains multiple connections to a SysDB server allowing to
54 handle multiple requests in parallel. The SysDB server is able to handle that
55 easily making it a cheap approach. The low-level Dial function creates a
56 single connection to a SysDB server allowing to perform low-level operations:
58 conn, err := client.Dial("unix:/var/run/sysdbd.sock", "username")
59 if err != nil {
60 // handle error
61 }
62 defer conn.Close()
64 The github.com/sysdb/go/proto package provides support for handling requests
65 and responses. Use the Send and Receive functions to communicate with the
66 server:
68 m := &proto.Message{
69 Type: proto.ConnectionQuery,
70 Raw: []byte{"LOOKUP hosts MATCHING attribute.architecture = 'amd64';"},
71 }
72 if err := conn.Send(m); err != nil {
73 // handle error
74 }
75 m, err := conn.Receive()
76 if err != nil {
77 // handle error
78 }
79 if m.Type == proto.ConnectionError {
80 // handle failed query
81 }
82 // ...
83 */
84 package client
86 import (
87 "encoding/binary"
88 "fmt"
89 "log"
90 "runtime"
92 "github.com/sysdb/go/proto"
93 )
95 // A Client is a client for SysDB.
96 //
97 // A client may be used from multiple goroutines in parallel.
98 type Client struct {
99 conns chan *Conn
100 }
102 // Connect creates a new client connected to a SysDB server instance at the
103 // specified address using the specified user.
104 //
105 // The address may be a IP address or a UNIX domain socket, either prefixed
106 // with 'unix:' or specifying an absolute file-system path.
107 func Connect(addr, user string) (*Client, error) {
108 c := &Client{conns: make(chan *Conn, 2*runtime.NumCPU())}
110 for i := 0; i < cap(c.conns); i++ {
111 conn, err := Dial(addr, user)
112 if err != nil {
113 return nil, err
114 }
115 c.conns <- conn
116 }
117 return c, nil
118 }
120 // Close closes a client connection. It may not be further used after calling
121 // this function.
122 //
123 // The function waits for all pending operations to finish.
124 func (c *Client) Close() {
125 for i := 0; i < cap(c.conns); i++ {
126 conn := <-c.conns
127 conn.Close()
128 }
129 close(c.conns)
130 c.conns = nil
131 }
133 // Call sends the specified request to the server and waits for its reply. It
134 // blocks until the full reply has been received.
135 func (c *Client) Call(req *proto.Message) (*proto.Message, error) {
136 conn := <-c.conns
137 defer func() { c.conns <- conn }()
139 err := conn.Send(req)
140 if err != nil {
141 return nil, err
142 }
144 for {
145 res, err := conn.Receive()
146 switch {
147 case err != nil:
148 return nil, err
149 case res.Type == proto.ConnectionError:
150 return nil, fmt.Errorf("request failed: %s", string(res.Raw))
151 case res.Type != proto.ConnectionLog:
152 return res, err
153 }
155 if len(res.Raw) > 4 {
156 log.Println(string(res.Raw[4:]))
157 }
158 }
159 }
161 // ServerVersion queries and returns the version of the remote server.
162 func (c *Client) ServerVersion() (major, minor, patch int, extra string, err error) {
163 res, err := c.Call(&proto.Message{Type: proto.ConnectionServerVersion})
164 if err != nil || res.Type != proto.ConnectionOK {
165 if err == nil {
166 err = fmt.Errorf("SERVER_VERSION command failed with status %d", res.Type)
167 }
168 return 0, 0, 0, "", err
169 }
170 if len(res.Raw) < 4 {
171 return 0, 0, 0, "", fmt.Errorf("SERVER_VERSION reply is too short")
172 }
173 version := int(binary.BigEndian.Uint32(res.Raw[:4]))
174 major = version / 10000
175 minor = version/100 - 100*major
176 patch = version - 10000*major - 100*minor
177 if len(res.Raw) > 4 {
178 extra = string(res.Raw[4:])
179 }
180 return major, minor, patch, extra, nil
181 }
183 // vim: set tw=78 sw=4 sw=4 noexpandtab :