mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-07-15 11:57:59 +00:00
Imported Upstream version 0.8
This commit is contained in:
@@ -2,13 +2,16 @@ language: go
|
||||
|
||||
go:
|
||||
- 1.2.1
|
||||
- 1.3
|
||||
- 1.3.1
|
||||
- tip
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc="
|
||||
|
||||
- secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E="
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y python-boto
|
||||
install:
|
||||
- make prepare
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ List of contributors, in chronological order:
|
||||
* Andrey Smirnov (https://github.com/smira)
|
||||
* Sebastien Binet (https://github.com/sbinet)
|
||||
* Ryan Uber (https://github.com/ryanuber)
|
||||
* Simon Aquino (https://github.com/simonaquino)
|
||||
* Simon Aquino (https://github.com/simonaquino)
|
||||
* Vincent Batoufflet (https://github.com/vbatoufflet)
|
||||
@@ -4,11 +4,14 @@ gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2'
|
||||
gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556'
|
||||
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
|
||||
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
|
||||
gom 'github.com/mitchellh/goamz/s3', :commit => '55f224c07975fddef9d2116600c664e30df3d594'
|
||||
gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1'
|
||||
gom 'github.com/mattn/go-shellwords', :commit => 'c7ca6f94add751566a61cf2199e1de78d4c3eee4'
|
||||
gom 'github.com/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405'
|
||||
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
|
||||
gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47'
|
||||
gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '9888007'
|
||||
gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107'
|
||||
gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa'
|
||||
gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'e2fa4e6ac1cc41a73bc9fd467878ecbf65df5cc3'
|
||||
gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b'
|
||||
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
|
||||
|
||||
|
||||
@@ -79,5 +79,6 @@ src-package:
|
||||
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
|
||||
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
|
||||
rm -rf aptly-$(VERSION)
|
||||
curl -T aptly-$(VERSION)-src.tar.bz2 -usmira:$(BINTRAY_KEY) https://api.bintray.com/content/smira/aptly/aptly/$(VERSION)/$(VERSION)/aptly-$(VERSION)-src.tar.bz2
|
||||
|
||||
.PHONY: coverage.out
|
||||
|
||||
@@ -11,9 +11,11 @@ aptly
|
||||
.. image:: http://gobuild.io/badge/github.com/smira/aptly/download.png
|
||||
:target: http://gobuild.io/github.com/smira/aptly
|
||||
|
||||
|
||||
Aptly is a swiss army knife for Debian repository management.
|
||||
|
||||
.. image:: http://www.aptly.info/img/aptly_logo.png
|
||||
:target: http://www.aptly.info/
|
||||
|
||||
Documentation is available at `http://www.aptly.info/ <http://www.aptly.info/>`_. For support use
|
||||
mailing list `aptly-discuss <https://groups.google.com/forum/#!forum/aptly-discuss>`_.
|
||||
|
||||
@@ -24,14 +26,14 @@ Aptly features: ("+" means planned features)
|
||||
* publish snapshot as Debian repository, ready to be consumed by apt
|
||||
* controlled update of one or more packages in snapshot from upstream mirror, tracking dependencies
|
||||
* merge two or more snapshots into one
|
||||
* filter repository by search query, pulling dependencies when required (+)
|
||||
* publish self-made packages as Debian repositories (+)
|
||||
* filter repository by search query, pulling dependencies when required
|
||||
* publish self-made packages as Debian repositories
|
||||
* mirror repositories "as-is" (without resigning with user's key) (+)
|
||||
* support for yum repositories (+)
|
||||
|
||||
Current limitations:
|
||||
|
||||
* debian-installer and translations not supported yet
|
||||
* translations are not supported yet
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2011-2013, Julien Laffaye <jlaffaye@FreeBSD.org>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
# goftp #
|
||||
|
||||
A FTP client package for Go
|
||||
|
||||
## Documentation ##
|
||||
|
||||
http://godoc.org/github.com/jlaffaye/ftp
|
||||
@@ -0,0 +1,132 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testData = "Just some text"
|
||||
testDir = "mydir"
|
||||
)
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
c, err := Connect("localhost:21")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.Login("anonymous", "anonymous")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.NoOp()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
data := bytes.NewBufferString(testData)
|
||||
err = c.Stor("test", data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = c.List(".")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = c.Rename("test", "tset")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r, err := c.Retr("tset")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(buf) != testData {
|
||||
t.Errorf("'%s'", buf)
|
||||
}
|
||||
r.Close()
|
||||
}
|
||||
|
||||
r, err = c.Retr("tset")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
r.Close()
|
||||
}
|
||||
|
||||
err = c.Delete("tset")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = c.MakeDir(testDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = c.ChangeDir(testDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dir, err := c.CurrentDir()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if dir != "/"+testDir {
|
||||
t.Error("Wrong dir: " + dir)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.ChangeDirToParent()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = c.RemoveDir(testDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = c.Logout()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
c.Quit()
|
||||
|
||||
err = c.NoOp()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
// ftp.mozilla.org uses multiline 220 response
|
||||
func TestConn2(t *testing.T) {
|
||||
c, err := Connect("ftp.mozilla.org:21")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.Login("anonymous", "anonymous")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = c.List(".")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
c.Quit()
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
// Package ftp implements a FTP client as described in RFC 959.
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EntryType describes the different types of an Entry.
|
||||
type EntryType int
|
||||
|
||||
const (
|
||||
EntryTypeFile EntryType = iota
|
||||
EntryTypeFolder
|
||||
EntryTypeLink
|
||||
)
|
||||
|
||||
// ServerConn represents the connection to a remote FTP server.
|
||||
type ServerConn struct {
|
||||
conn *textproto.Conn
|
||||
host string
|
||||
features map[string]string
|
||||
}
|
||||
|
||||
// Entry describes a file and is returned by List().
|
||||
type Entry struct {
|
||||
Name string
|
||||
Type EntryType
|
||||
Size uint64
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// response represent a data-connection
|
||||
type response struct {
|
||||
conn net.Conn
|
||||
c *ServerConn
|
||||
}
|
||||
|
||||
// Connect initializes the connection to the specified ftp server address.
|
||||
//
|
||||
// It is generally followed by a call to Login() as most FTP commands require
|
||||
// an authenticated user.
|
||||
func Connect(addr string) (*ServerConn, error) {
|
||||
conn, err := textproto.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := strings.SplitN(addr, ":", 2)
|
||||
c := &ServerConn{
|
||||
conn: conn,
|
||||
host: a[0],
|
||||
features: make(map[string]string),
|
||||
}
|
||||
|
||||
_, _, err = c.conn.ReadResponse(StatusReady)
|
||||
if err != nil {
|
||||
c.Quit()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.feat()
|
||||
if err != nil {
|
||||
c.Quit()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Login authenticates the client with specified user and password.
|
||||
//
|
||||
// "anonymous"/"anonymous" is a common user/password scheme for FTP servers
|
||||
// that allows anonymous read-only accounts.
|
||||
func (c *ServerConn) Login(user, password string) error {
|
||||
code, message, err := c.cmd(-1, "USER %s", user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch code {
|
||||
case StatusLoggedIn:
|
||||
case StatusUserOK:
|
||||
_, _, err = c.cmd(StatusLoggedIn, "PASS %s", password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New(message)
|
||||
}
|
||||
|
||||
// Switch to binary mode
|
||||
_, _, err = c.cmd(StatusCommandOK, "TYPE I")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// feat issues a FEAT FTP command to list the additional commands supported by
|
||||
// the remote FTP server.
|
||||
// FEAT is described in RFC 2389
|
||||
func (c *ServerConn) feat() error {
|
||||
code, message, err := c.cmd(-1, "FEAT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code != StatusSystem {
|
||||
// The server does not support the FEAT command. This is not an
|
||||
// error: we consider that there is no additional feature.
|
||||
return nil
|
||||
}
|
||||
|
||||
lines := strings.Split(message, "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.HasPrefix(line, " ") {
|
||||
continue
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
featureElements := strings.SplitN(line, " ", 2)
|
||||
|
||||
command := featureElements[0]
|
||||
|
||||
var commandDesc string
|
||||
if len(featureElements) == 2 {
|
||||
commandDesc = featureElements[1]
|
||||
}
|
||||
|
||||
c.features[command] = commandDesc
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// epsv issues an "EPSV" command to get a port number for a data connection.
|
||||
func (c *ServerConn) epsv() (port int, err error) {
|
||||
_, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
start := strings.Index(line, "|||")
|
||||
end := strings.LastIndex(line, "|")
|
||||
if start == -1 || end == -1 {
|
||||
err = errors.New("Invalid EPSV response format")
|
||||
return
|
||||
}
|
||||
port, err = strconv.Atoi(line[start+3 : end])
|
||||
return
|
||||
}
|
||||
|
||||
// pasv issues a "PASV" command to get a port number for a data connection.
|
||||
func (c *ServerConn) pasv() (port int, err error) {
|
||||
_, line, err := c.cmd(StatusPassiveMode, "PASV")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
|
||||
start := strings.Index(line, "(")
|
||||
end := strings.LastIndex(line, ")")
|
||||
if start == -1 || end == -1 {
|
||||
err = errors.New("Invalid EPSV response format")
|
||||
return
|
||||
}
|
||||
|
||||
// We have to split the response string
|
||||
pasvData := strings.Split(line[start+1:end], ",")
|
||||
// Let's compute the port number
|
||||
portPart1, err1 := strconv.Atoi(pasvData[4])
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
|
||||
portPart2, err2 := strconv.Atoi(pasvData[5])
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
return
|
||||
}
|
||||
|
||||
// Recompose port
|
||||
port = portPart1*256 + portPart2
|
||||
return
|
||||
}
|
||||
|
||||
// openDataConn creates a new FTP data connection.
|
||||
func (c *ServerConn) openDataConn() (net.Conn, error) {
|
||||
var port int
|
||||
var err error
|
||||
|
||||
// If features contains nat6 or EPSV => EPSV
|
||||
// else -> PASV
|
||||
_, nat6Supported := c.features["nat6"]
|
||||
_, epsvSupported := c.features["EPSV"]
|
||||
if nat6Supported || epsvSupported {
|
||||
port, err = c.epsv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
port, err = c.pasv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Build the new net address string
|
||||
addr := fmt.Sprintf("%s:%d", c.host, port)
|
||||
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// cmd is a helper function to execute a command and check for the expected FTP
|
||||
// return code
|
||||
func (c *ServerConn) cmd(expected int, format string, args ...interface{}) (int, string, error) {
|
||||
_, err := c.conn.Cmd(format, args...)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
code, line, err := c.conn.ReadResponse(expected)
|
||||
return code, line, err
|
||||
}
|
||||
|
||||
// cmdDataConn executes a command which require a FTP data connection.
|
||||
func (c *ServerConn) cmdDataConn(format string, args ...interface{}) (net.Conn, error) {
|
||||
conn, err := c.openDataConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.conn.Cmd(format, args...)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
code, msg, err := c.conn.ReadCodeLine(-1)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if code != StatusAlreadyOpen && code != StatusAboutToSend {
|
||||
conn.Close()
|
||||
return nil, &textproto.Error{code, msg}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// parseListLine parses the various non-standard format returned by the LIST
|
||||
// FTP command.
|
||||
func parseListLine(line string) (*Entry, error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 9 {
|
||||
return nil, errors.New("Unsupported LIST line")
|
||||
}
|
||||
|
||||
e := &Entry{}
|
||||
switch fields[0][0] {
|
||||
case '-':
|
||||
e.Type = EntryTypeFile
|
||||
case 'd':
|
||||
e.Type = EntryTypeFolder
|
||||
case 'l':
|
||||
e.Type = EntryTypeLink
|
||||
default:
|
||||
return nil, errors.New("Unknown entry type")
|
||||
}
|
||||
|
||||
if e.Type == EntryTypeFile {
|
||||
size, err := strconv.ParseUint(fields[4], 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Size = size
|
||||
}
|
||||
var timeStr string
|
||||
if strings.Contains(fields[7], ":") { // this year
|
||||
thisYear, _, _ := time.Now().Date()
|
||||
timeStr = fields[6] + " " + fields[5] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[7] + " GMT"
|
||||
} else { // not this year
|
||||
timeStr = fields[6] + " " + fields[5] + " " + fields[7][2:4] + " " + "00:00" + " GMT"
|
||||
}
|
||||
t, err := time.Parse("_2 Jan 06 15:04 MST", timeStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Time = t
|
||||
|
||||
e.Name = strings.Join(fields[8:], " ")
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// NameList issues an NLST FTP command.
|
||||
func (c *ServerConn) NameList(path string) (entries []string, err error) {
|
||||
conn, err := c.cmdDataConn("NLST %s", path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := &response{conn, c}
|
||||
defer r.Close()
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
entries = append(entries, scanner.Text())
|
||||
}
|
||||
if err = scanner.Err(); err != nil {
|
||||
return entries, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// List issues a LIST FTP command.
|
||||
func (c *ServerConn) List(path string) (entries []*Entry, err error) {
|
||||
conn, err := c.cmdDataConn("LIST %s", path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := &response{conn, c}
|
||||
defer r.Close()
|
||||
|
||||
bio := bufio.NewReader(r)
|
||||
for {
|
||||
line, e := bio.ReadString('\n')
|
||||
if e == io.EOF {
|
||||
break
|
||||
} else if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
entry, err := parseListLine(line)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeDir issues a CWD FTP command, which changes the current directory to
|
||||
// the specified path.
|
||||
func (c *ServerConn) ChangeDir(path string) error {
|
||||
_, _, err := c.cmd(StatusRequestedFileActionOK, "CWD %s", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeDirToParent issues a CDUP FTP command, which changes the current
|
||||
// directory to the parent directory. This is similar to a call to ChangeDir
|
||||
// with a path set to "..".
|
||||
func (c *ServerConn) ChangeDirToParent() error {
|
||||
_, _, err := c.cmd(StatusRequestedFileActionOK, "CDUP")
|
||||
return err
|
||||
}
|
||||
|
||||
// CurrentDir issues a PWD FTP command, which Returns the path of the current
|
||||
// directory.
|
||||
func (c *ServerConn) CurrentDir() (string, error) {
|
||||
_, msg, err := c.cmd(StatusPathCreated, "PWD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
start := strings.Index(msg, "\"")
|
||||
end := strings.LastIndex(msg, "\"")
|
||||
|
||||
if start == -1 || end == -1 {
|
||||
return "", errors.New("Unsuported PWD response format")
|
||||
}
|
||||
|
||||
return msg[start+1 : end], nil
|
||||
}
|
||||
|
||||
// Retr issues a RETR FTP command to fetch the specified file from the remote
|
||||
// FTP server.
|
||||
//
|
||||
// The returned ReadCloser must be closed to cleanup the FTP data connection.
|
||||
func (c *ServerConn) Retr(path string) (io.ReadCloser, error) {
|
||||
conn, err := c.cmdDataConn("RETR %s", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &response{conn, c}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Stor issues a STOR FTP command to store a file to the remote FTP server.
|
||||
// Stor creates the specified file with the content of the io.Reader.
|
||||
//
|
||||
// Hint: io.Pipe() can be used if an io.Writer is required.
|
||||
func (c *ServerConn) Stor(path string, r io.Reader) error {
|
||||
conn, err := c.cmdDataConn("STOR %s", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(conn, r)
|
||||
conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename renames a file on the remote FTP server.
|
||||
func (c *ServerConn) Rename(from, to string) error {
|
||||
_, _, err := c.cmd(StatusRequestFilePending, "RNFR %s", from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = c.cmd(StatusRequestedFileActionOK, "RNTO %s", to)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete issues a DELE FTP command to delete the specified file from the
|
||||
// remote FTP server.
|
||||
func (c *ServerConn) Delete(path string) error {
|
||||
_, _, err := c.cmd(StatusRequestedFileActionOK, "DELE %s", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// MakeDir issues a MKD FTP command to create the specified directory on the
|
||||
// remote FTP server.
|
||||
func (c *ServerConn) MakeDir(path string) error {
|
||||
_, _, err := c.cmd(StatusPathCreated, "MKD %s", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveDir issues a RMD FTP command to remove the specified directory from
|
||||
// the remote FTP server.
|
||||
func (c *ServerConn) RemoveDir(path string) error {
|
||||
_, _, err := c.cmd(StatusRequestedFileActionOK, "RMD %s", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// NoOp issues a NOOP FTP command.
|
||||
// NOOP has no effects and is usually used to prevent the remote FTP server to
|
||||
// close the otherwise idle connection.
|
||||
func (c *ServerConn) NoOp() error {
|
||||
_, _, err := c.cmd(StatusCommandOK, "NOOP")
|
||||
return err
|
||||
}
|
||||
|
||||
// Logout issues a REIN FTP command to logout the current user.
|
||||
func (c *ServerConn) Logout() error {
|
||||
_, _, err := c.cmd(StatusLoggedIn, "REIN")
|
||||
return err
|
||||
}
|
||||
|
||||
// Quit issues a QUIT FTP command to properly close the connection from the
|
||||
// remote FTP server.
|
||||
func (c *ServerConn) Quit() error {
|
||||
c.conn.Cmd("QUIT")
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface on a FTP data connection.
|
||||
func (r *response) Read(buf []byte) (int, error) {
|
||||
n, err := r.conn.Read(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface on a FTP data connection.
|
||||
func (r *response) Close() error {
|
||||
err := r.conn.Close()
|
||||
_, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var thisYear, _, _ = time.Now().Date()
|
||||
|
||||
type line struct {
|
||||
line string
|
||||
name string
|
||||
size uint64
|
||||
entryType EntryType
|
||||
time time.Time
|
||||
}
|
||||
|
||||
var listTests = []line{
|
||||
// UNIX ls -l style
|
||||
line{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 pub", "pub", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
|
||||
line{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 p u b", "p u b", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
|
||||
line{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 fileName", "fileName", 1234567, EntryTypeFile, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)},
|
||||
line{"lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin", "bin -> usr/bin", 0, EntryTypeLink, time.Date(thisYear, time.January, 25, 0, 17, 0, 0, time.UTC)},
|
||||
// Microsoft's FTP servers for Windows
|
||||
line{"---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z", "ls-lR.Z", 1803128, EntryTypeFile, time.Date(thisYear, time.July, 10, 10, 18, 0, 0, time.UTC)},
|
||||
line{"d--------- 1 owner group 0 May 9 19:45 Softlib", "Softlib", 0, EntryTypeFolder, time.Date(thisYear, time.May, 9, 19, 45, 0, 0, time.UTC)},
|
||||
// WFTPD for MSDOS
|
||||
line{"-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp", "message.ftp", 322, EntryTypeFile, time.Date(1996, time.August, 19, 0, 0, 0, 0, time.UTC)},
|
||||
}
|
||||
|
||||
// Not supported, at least we should properly return failure
|
||||
var listTestsFail = []line{
|
||||
line{"d [R----F--] supervisor 512 Jan 16 18:53 login", "login", 0, EntryTypeFolder, time.Date(thisYear, time.January, 16, 18, 53, 0, 0, time.UTC)},
|
||||
line{"- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe", "cx.exe", 0, EntryTypeFile, time.Date(thisYear, time.October, 20, 15, 27, 0, 0, time.UTC)},
|
||||
}
|
||||
|
||||
func TestParseListLine(t *testing.T) {
|
||||
for _, lt := range listTests {
|
||||
entry, err := parseListLine(lt.line)
|
||||
if err != nil {
|
||||
t.Errorf("parseListLine(%v) returned err = %v", lt.line, err)
|
||||
continue
|
||||
}
|
||||
if entry.Name != lt.name {
|
||||
t.Errorf("parseListLine(%v).Name = '%v', want '%v'", lt.line, entry.Name, lt.name)
|
||||
}
|
||||
if entry.Type != lt.entryType {
|
||||
t.Errorf("parseListLine(%v).EntryType = %v, want %v", lt.line, entry.Type, lt.entryType)
|
||||
}
|
||||
if entry.Size != lt.size {
|
||||
t.Errorf("parseListLine(%v).Size = %v, want %v", lt.line, entry.Size, lt.size)
|
||||
}
|
||||
if entry.Time.Unix() != lt.time.Unix() {
|
||||
t.Errorf("parseListLine(%v).Time = %v, want %v", lt.line, entry.Time, lt.time)
|
||||
}
|
||||
}
|
||||
for _, lt := range listTestsFail {
|
||||
_, err := parseListLine(lt.line)
|
||||
if err == nil {
|
||||
t.Errorf("parseListLine(%v) expected to fail", lt.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package ftp
|
||||
|
||||
const (
|
||||
// Positive Preliminary reply
|
||||
StatusInitiating = 100
|
||||
StatusRestartMarker = 110
|
||||
StatusReadyMinute = 120
|
||||
StatusAlreadyOpen = 125
|
||||
StatusAboutToSend = 150
|
||||
|
||||
// Positive Completion reply
|
||||
StatusCommandOK = 200
|
||||
StatusCommandNotImplemented = 202
|
||||
StatusSystem = 211
|
||||
StatusDirectory = 212
|
||||
StatusFile = 213
|
||||
StatusHelp = 214
|
||||
StatusName = 215
|
||||
StatusReady = 220
|
||||
StatusClosing = 221
|
||||
StatusDataConnectionOpen = 225
|
||||
StatusClosingDataConnection = 226
|
||||
StatusPassiveMode = 227
|
||||
StatusLongPassiveMode = 228
|
||||
StatusExtendedPassiveMode = 229
|
||||
StatusLoggedIn = 230
|
||||
StatusLoggedOut = 231
|
||||
StatusLogoutAck = 232
|
||||
StatusRequestedFileActionOK = 250
|
||||
StatusPathCreated = 257
|
||||
|
||||
// Positive Intermediate reply
|
||||
StatusUserOK = 331
|
||||
StatusLoginNeedAccount = 332
|
||||
StatusRequestFilePending = 350
|
||||
|
||||
// Transient Negative Completion reply
|
||||
StatusNotAvailable = 421
|
||||
StatusCanNotOpenDataConnection = 425
|
||||
StatusTransfertAborted = 426
|
||||
StatusInvalidCredentials = 430
|
||||
StatusHostUnavailable = 434
|
||||
StatusFileActionIgnored = 450
|
||||
StatusActionAborted = 451
|
||||
Status452 = 452
|
||||
|
||||
// Permanent Negative Completion reply
|
||||
StatusBadCommand = 500
|
||||
StatusBadArguments = 501
|
||||
StatusNotImplemented = 502
|
||||
StatusBadSequence = 503
|
||||
StatusNotImplementedParameter = 504
|
||||
StatusNotLoggedIn = 530
|
||||
StatusStorNeedAccount = 532
|
||||
StatusFileUnavailable = 550
|
||||
StatusPageTypeUnknown = 551
|
||||
StatusExceededStorage = 552
|
||||
StatusBadFileName = 553
|
||||
)
|
||||
|
||||
var statusText = map[int]string{
|
||||
// 200
|
||||
StatusCommandOK: "Command okay.",
|
||||
StatusCommandNotImplemented: "Command not implemented, superfluous at this site.",
|
||||
StatusSystem: "System status, or system help reply.",
|
||||
StatusDirectory: "Directory status.",
|
||||
StatusFile: "File status.",
|
||||
StatusHelp: "Help message.",
|
||||
StatusName: "",
|
||||
StatusReady: "Service ready for new user.",
|
||||
StatusClosing: "Service closing control connection.",
|
||||
StatusDataConnectionOpen: "Data connection open; no transfer in progress.",
|
||||
StatusClosingDataConnection: "Closing data connection. Requested file action successful.",
|
||||
StatusPassiveMode: "Entering Passive Mode.",
|
||||
StatusLongPassiveMode: "Entering Long Passive Mode.",
|
||||
StatusExtendedPassiveMode: "Entering Extended Passive Mode.",
|
||||
StatusLoggedIn: "User logged in, proceed.",
|
||||
StatusLoggedOut: "User logged out; service terminated.",
|
||||
StatusLogoutAck: "Logout command noted, will complete when transfer done.",
|
||||
StatusRequestedFileActionOK: "Requested file action okay, completed.",
|
||||
StatusPathCreated: "Path created.",
|
||||
|
||||
// 300
|
||||
StatusUserOK: "User name okay, need password.",
|
||||
StatusLoginNeedAccount: "Need account for login.",
|
||||
StatusRequestFilePending: "Requested file action pending further information.",
|
||||
|
||||
// 400
|
||||
StatusNotAvailable: "Service not available, closing control connection.",
|
||||
StatusCanNotOpenDataConnection: "Can't open data connection.",
|
||||
StatusTransfertAborted: "Connection closed; transfer aborted.",
|
||||
StatusInvalidCredentials: "Invalid username or password.",
|
||||
StatusHostUnavailable: "Requested host unavailable.",
|
||||
StatusFileActionIgnored: "Requested file action not taken.",
|
||||
StatusActionAborted: "Requested action aborted. Local error in processing.",
|
||||
Status452: "Insufficient storage space in system.",
|
||||
|
||||
// 500
|
||||
StatusBadCommand: "Command unrecognized.",
|
||||
StatusBadArguments: "Syntax error in parameters or arguments.",
|
||||
StatusNotImplemented: "Command not implemented.",
|
||||
StatusBadSequence: "Bad sequence of commands.",
|
||||
StatusNotImplementedParameter: "Command not implemented for that parameter.",
|
||||
StatusNotLoggedIn: "Not logged in.",
|
||||
StatusStorNeedAccount: "Need account for storing files.",
|
||||
StatusFileUnavailable: "File unavailable.",
|
||||
StatusPageTypeUnknown: "Page type unknown.",
|
||||
StatusExceededStorage: "Exceeded storage allocation.",
|
||||
StatusBadFileName: "File name not allowed.",
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get code.google.com/p/go.tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL
|
||||
@@ -0,0 +1,40 @@
|
||||
# go-shellwords
|
||||
|
||||
[](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||
[](https://travis-ci.org/mattn/go-shellwords)
|
||||
|
||||
Parse line as shell words.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
args, err := shellwords.Parse("./foo --bar=baz")
|
||||
// args should be ["./foo", "--bar=baz"]
|
||||
```
|
||||
|
||||
```go
|
||||
os.Setenv("FOO", "bar")
|
||||
p := shellwords.NewParser()
|
||||
p.ParseEnv = true
|
||||
args, err := p.Parse("./foo $FOO")
|
||||
// args should be ["./foo", "bar"]
|
||||
```
|
||||
|
||||
```go
|
||||
p := shellwords.NewParser()
|
||||
p.ParseBacktick = true
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||
|
||||
# License
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2014
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
@@ -0,0 +1,129 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceEnv(s string) string {
|
||||
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||
s = s[1:]
|
||||
if s[0] == '{' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
return os.Getenv(s)
|
||||
})
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
ParseEnv bool
|
||||
ParseBacktick bool
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return new(Parser)
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(line string) ([]string, error) {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
args := []string{}
|
||||
buf := ""
|
||||
var escaped, doubleQuoted, singleQuoted, backQuote bool
|
||||
backtick := ""
|
||||
|
||||
for _, r := range line {
|
||||
if escaped {
|
||||
buf += string(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf += string(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSpace(r) {
|
||||
if singleQuoted || doubleQuoted || backQuote {
|
||||
buf += string(r)
|
||||
backtick += string(r)
|
||||
} else if buf != "" {
|
||||
if p.ParseEnv {
|
||||
buf = replaceEnv(buf)
|
||||
}
|
||||
args = append(args, buf)
|
||||
buf = ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '`':
|
||||
if !singleQuoted && !doubleQuoted {
|
||||
if p.ParseBacktick {
|
||||
if backQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
continue
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
}
|
||||
case '"':
|
||||
if !singleQuoted {
|
||||
doubleQuoted = !doubleQuoted
|
||||
continue
|
||||
}
|
||||
case '\'':
|
||||
if !doubleQuoted {
|
||||
singleQuoted = !singleQuoted
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
buf += string(r)
|
||||
if backQuote {
|
||||
backtick += string(r)
|
||||
}
|
||||
}
|
||||
|
||||
if buf != "" {
|
||||
if p.ParseEnv {
|
||||
buf = replaceEnv(buf)
|
||||
}
|
||||
args = append(args, buf)
|
||||
}
|
||||
|
||||
if escaped || singleQuoted || doubleQuoted || backQuote {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func Parse(line string) ([]string, error) {
|
||||
return NewParser().Parse(line)
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testcases = []struct {
|
||||
line string
|
||||
expected []string
|
||||
}{
|
||||
{`var --bar=baz`, []string{`var`, `--bar=baz`}},
|
||||
{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
|
||||
{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
|
||||
{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
|
||||
{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
|
||||
{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
|
||||
{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
|
||||
{`var --bar='\'`, []string{`var`, `--bar=\`}},
|
||||
{`var "--bar baz"`, []string{`var`, `--bar baz`}},
|
||||
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
args, err := Parse(testcase.line)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(args, testcase.expected) {
|
||||
t.Fatalf("Expected %v, but %v:", testcase.expected, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
_, err := Parse("foo '")
|
||||
if err == nil {
|
||||
t.Fatalf("Should be an error")
|
||||
}
|
||||
_, err = Parse(`foo "`)
|
||||
if err == nil {
|
||||
t.Fatalf("Should be an error")
|
||||
}
|
||||
|
||||
_, err = Parse("foo `")
|
||||
if err == nil {
|
||||
t.Fatalf("Should be an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktick(t *testing.T) {
|
||||
goversion, err := shellRun("go version")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseBacktick = true
|
||||
args, err := parser.Parse("echo `go version`")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected := []string{"echo", goversion}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %v, but %v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktickError(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.ParseBacktick = true
|
||||
_, err := parser.Parse("echo `go Version`")
|
||||
if err == nil {
|
||||
t.Fatalf("Should be an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
os.Setenv("FOO", "bar")
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $FOO")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected := []string{"echo", "bar"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %v, but %v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoEnv(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $BAR")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected := []string{"echo", ""}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %v, but %v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDupEnv(t *testing.T) {
|
||||
os.Setenv("FOO", "bar")
|
||||
os.Setenv("FOO_BAR", "baz")
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $$FOO$")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected := []string{"echo", "$bar$"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %v, but %v:", expected, args)
|
||||
}
|
||||
|
||||
args, err = parser.Parse("echo $${FOO_BAR}$")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected = []string{"echo", "$baz$"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %v, but %v:", expected, args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// +build !windows
|
||||
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("SHELL")
|
||||
b, err := exec.Command(shell, "-c", line).Output()
|
||||
if err != nil {
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("COMSPEC")
|
||||
b, err := exec.Command(shell, "/c", line).Output()
|
||||
if err != nil {
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -1,4 +1,41 @@
|
||||
# goamz - An Amazon Library for Go
|
||||
# goamz - An Amazon Library for Go
|
||||
|
||||
Current API documentation: [](https://godoc.org/github.com/mitchellh/goamz)
|
||||
|
||||
This is a fork of [https://launchpad.net/goamz](https://launchpad.net/goamz)
|
||||
that adds some missing API calls to certain packages.
|
||||
|
||||
This library is *incomplete*, but implements a large amount of the AWS API.
|
||||
It is heavily used in projects such as
|
||||
[Terraform](https://github.com/hashicorp/terraform) and
|
||||
[Packer](https://github.com/mitchellh/packer).
|
||||
If you find anything missing from this library,
|
||||
please [file an issue](https://github.com/mitchellh/goamz).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/s3"
|
||||
"log"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
client := s3.New(auth, aws.USEast)
|
||||
resp, err := client.ListBuckets()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print(fmt.Sprintf("%T %+v", resp.Buckets[0], resp.Buckets[0]))
|
||||
}
|
||||
```
|
||||
|
||||
+25
-6
@@ -4,12 +4,13 @@ package autoscaling
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
)
|
||||
|
||||
// The AutoScaling type encapsulates operations operations with the autoscaling endpoint.
|
||||
@@ -109,11 +110,13 @@ type LoadBalancerName struct {
|
||||
}
|
||||
|
||||
type LaunchConfiguration struct {
|
||||
ImageId string `xml:"member>ImageId"`
|
||||
InstanceType string `xml:"member>InstanceType"`
|
||||
KeyName string `xml:"member>KeyName"`
|
||||
Name string `xml:"member>LaunchConfigurationName"`
|
||||
SecurityGroups []SecurityGroup `xml:"member>SecurityGroups"`
|
||||
IamInstanceProfile string `xml:"member>IamInstanceProfile"`
|
||||
ImageId string `xml:"member>ImageId"`
|
||||
InstanceType string `xml:"member>InstanceType"`
|
||||
KeyName string `xml:"member>KeyName"`
|
||||
Name string `xml:"member>LaunchConfigurationName"`
|
||||
SecurityGroups []SecurityGroup `xml:"member>SecurityGroups"`
|
||||
UserData []byte `xml:"member>UserData"`
|
||||
}
|
||||
|
||||
type AutoScalingGroup struct {
|
||||
@@ -147,6 +150,7 @@ type CreateAutoScalingGroup struct {
|
||||
MaxSize int
|
||||
MinSize int
|
||||
PlacementGroup string
|
||||
TerminationPolicies []string
|
||||
Name string
|
||||
Tags []Tag
|
||||
VPCZoneIdentifier []string
|
||||
@@ -212,6 +216,10 @@ func (autoscaling *AutoScaling) CreateAutoScalingGroup(options *CreateAutoScalin
|
||||
params["Tag.member."+strconv.Itoa(j+1)+".Value"] = tag.Value
|
||||
}
|
||||
|
||||
for i, v := range options.TerminationPolicies {
|
||||
params["TerminationPolicies.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
if options.VPCZoneIdentifier != nil {
|
||||
params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",")
|
||||
}
|
||||
@@ -235,6 +243,7 @@ type CreateLaunchConfiguration struct {
|
||||
KeyName string
|
||||
Name string
|
||||
SecurityGroups []string
|
||||
UserData string
|
||||
}
|
||||
|
||||
func (autoscaling *AutoScaling) CreateLaunchConfiguration(options *CreateLaunchConfiguration) (resp *SimpleResp, err error) {
|
||||
@@ -260,6 +269,12 @@ func (autoscaling *AutoScaling) CreateLaunchConfiguration(options *CreateLaunchC
|
||||
params["SecurityGroups.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
if options.UserData != "" {
|
||||
userData := make([]byte, b64.EncodedLen(len(options.UserData)))
|
||||
b64.Encode(userData, []byte(options.UserData))
|
||||
params["UserData"] = string(userData)
|
||||
}
|
||||
|
||||
resp = &SimpleResp{}
|
||||
|
||||
err = autoscaling.query(params, resp)
|
||||
@@ -389,6 +404,7 @@ type UpdateAutoScalingGroup struct {
|
||||
MaxSize int
|
||||
MinSize int
|
||||
PlacementGroup string
|
||||
TerminationPolicies []string
|
||||
Name string
|
||||
VPCZoneIdentifier []string
|
||||
|
||||
@@ -441,6 +457,9 @@ func (autoscaling *AutoScaling) UpdateAutoScalingGroup(options *UpdateAutoScalin
|
||||
if options.PlacementGroup != "" {
|
||||
params["PlacementGroup"] = options.PlacementGroup
|
||||
}
|
||||
for i, v := range options.TerminationPolicies {
|
||||
params["TerminationPolicies.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
if options.VPCZoneIdentifier != nil {
|
||||
params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",")
|
||||
|
||||
+7
-1
@@ -1,11 +1,12 @@
|
||||
package autoscaling_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/goamz/autoscaling"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/testutil"
|
||||
. "github.com/motain/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
@@ -44,6 +45,7 @@ func (s *S) Test_CreateAutoScalingGroup(c *C) {
|
||||
MinSize: 2,
|
||||
MaxSize: 2,
|
||||
PlacementGroup: "foobar",
|
||||
TerminationPolicies: []string{"ClosestToNextInstanceHour", "OldestInstance"},
|
||||
Name: "foobar",
|
||||
Tags: []autoscaling.Tag{
|
||||
autoscaling.Tag{
|
||||
@@ -60,6 +62,8 @@ func (s *S) Test_CreateAutoScalingGroup(c *C) {
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateAutoScalingGroup"})
|
||||
c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-foo"})
|
||||
c.Assert(req.Form["VPCZoneIdentifier"], DeepEquals, []string{"foo,bar"})
|
||||
c.Assert(req.Form["TerminationPolicies.member.1"], DeepEquals, []string{"ClosestToNextInstanceHour"})
|
||||
c.Assert(req.Form["TerminationPolicies.member.2"], DeepEquals, []string{"OldestInstance"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "8d798a29-f083-11e1-bdfb-cb223EXAMPLE")
|
||||
}
|
||||
@@ -74,6 +78,7 @@ func (s *S) Test_CreateLaunchConfiguration(c *C) {
|
||||
InstanceType: "m1.small",
|
||||
KeyName: "foobar",
|
||||
Name: "i-141421",
|
||||
UserData: "#!/bin/bash\necho Hello\n",
|
||||
}
|
||||
|
||||
resp, err := s.autoscaling.CreateLaunchConfiguration(&options)
|
||||
@@ -82,6 +87,7 @@ func (s *S) Test_CreateLaunchConfiguration(c *C) {
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateLaunchConfiguration"})
|
||||
c.Assert(req.Form["InstanceType"], DeepEquals, []string{"m1.small"})
|
||||
c.Assert(req.Form["SecurityGroups.member.1"], DeepEquals, []string{"sg-1111"})
|
||||
c.Assert(req.Form["UserData"], DeepEquals, []string{"IyEvYmluL2Jhc2gKZWNobyBIZWxsbwo="})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7c6e177f-f082-11e1-ac58-3714bEXAMPLE")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// Region defines the URLs where AWS services may be accessed.
|
||||
@@ -190,6 +192,23 @@ var SAEast = Region{
|
||||
"https://route53.amazonaws.com",
|
||||
}
|
||||
|
||||
var CNNorth = Region{
|
||||
"cn-north-1",
|
||||
"https://ec2.cn-north-1.amazonaws.com.cn",
|
||||
"https://s3.cn-north-1.amazonaws.com.cn",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"",
|
||||
"https://sns.cn-north-1.amazonaws.com.cn",
|
||||
"https://sqs.cn-north-1.amazonaws.com.cn",
|
||||
"https://iam.cn-north-1.amazonaws.com.cn",
|
||||
"https://elasticloadbalancing.cn-north-1.amazonaws.com.cn",
|
||||
"https://autoscaling.cn-north-1.amazonaws.com.cn",
|
||||
"https://rds.cn-north-1.amazonaws.com.cn",
|
||||
"https://route53.amazonaws.com",
|
||||
}
|
||||
|
||||
var Regions = map[string]Region{
|
||||
APNortheast.Name: APNortheast,
|
||||
APSoutheast.Name: APSoutheast,
|
||||
@@ -200,6 +219,7 @@ var Regions = map[string]Region{
|
||||
USWest2.Name: USWest2,
|
||||
SAEast.Name: SAEast,
|
||||
USGovWest.Name: USGovWest,
|
||||
CNNorth.Name: CNNorth,
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
@@ -278,6 +298,13 @@ func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
|
||||
return Auth{accessKey, secretKey, ""}, nil
|
||||
}
|
||||
|
||||
// Next try to get auth from the environment
|
||||
auth, err = SharedAuth()
|
||||
if err == nil {
|
||||
// Found auth, return
|
||||
return
|
||||
}
|
||||
|
||||
// Next try to get auth from the environment
|
||||
auth, err = EnvAuth()
|
||||
if err == nil {
|
||||
@@ -298,8 +325,53 @@ func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SharedAuth creates an Auth based on shared credentials stored in
|
||||
// $HOME/.aws/credentials. The AWS_PROFILE environment variables is used to
|
||||
// select the profile.
|
||||
func SharedAuth() (auth Auth, err error) {
|
||||
var profileName = os.Getenv("AWS_PROFILE")
|
||||
|
||||
if profileName == "" {
|
||||
profileName = "default"
|
||||
}
|
||||
|
||||
var credentialsFile = os.Getenv("AWS_CREDENTIAL_FILE")
|
||||
if credentialsFile == "" {
|
||||
var homeDir = os.Getenv("HOME")
|
||||
if homeDir == "" {
|
||||
err = errors.New("Could not get HOME")
|
||||
return
|
||||
}
|
||||
credentialsFile = homeDir + "/.aws/credentials"
|
||||
}
|
||||
|
||||
file, err := ini.LoadFile(credentialsFile)
|
||||
if err != nil {
|
||||
err = errors.New("Couldn't parse AWS credentials file")
|
||||
return
|
||||
}
|
||||
|
||||
var profile = file[profileName]
|
||||
if profile == nil {
|
||||
err = errors.New("Couldn't find profile in AWS credentials file")
|
||||
return
|
||||
}
|
||||
|
||||
auth.AccessKey = profile["aws_access_key_id"]
|
||||
auth.SecretKey = profile["aws_secret_access_key"]
|
||||
|
||||
if auth.AccessKey == "" {
|
||||
err = errors.New("AWS_ACCESS_KEY_ID not found in environment in credentials file")
|
||||
}
|
||||
if auth.SecretKey == "" {
|
||||
err = errors.New("AWS_SECRET_ACCESS_KEY not found in credentials file")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EnvAuth creates an Auth based on environment information.
|
||||
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
|
||||
// For accounts that require a security token, it is read from AWS_SECURITY_TOKEN
|
||||
// variables are used.
|
||||
func EnvAuth() (auth Auth, err error) {
|
||||
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
@@ -311,6 +383,9 @@ func EnvAuth() (auth Auth, err error) {
|
||||
if auth.SecretKey == "" {
|
||||
auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
|
||||
}
|
||||
|
||||
auth.Token = os.Getenv("AWS_SECURITY_TOKEN")
|
||||
|
||||
if auth.AccessKey == "" {
|
||||
err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package aws_test
|
||||
import (
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
. "github.com/motain/gocheck"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -30,6 +31,110 @@ func (s *S) TearDownTest(c *C) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuthNoHome(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_PROFILE", "foo")
|
||||
_, err := aws.SharedAuth()
|
||||
c.Assert(err, ErrorMatches, "Could not get HOME")
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuthNoCredentialsFile(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_PROFILE", "foo")
|
||||
os.Setenv("HOME", "/tmp")
|
||||
_, err := aws.SharedAuth()
|
||||
c.Assert(err, ErrorMatches, "Couldn't parse AWS credentials file")
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuthNoProfileInFile(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_PROFILE", "foo")
|
||||
|
||||
d, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
err = os.Mkdir(d+"/.aws", 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\n"), 0644)
|
||||
os.Setenv("HOME", d)
|
||||
|
||||
_, err = aws.SharedAuth()
|
||||
c.Assert(err, ErrorMatches, "Couldn't find profile in AWS credentials file")
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuthNoKeysInProfile(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_PROFILE", "bar")
|
||||
|
||||
d, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
err = os.Mkdir(d+"/.aws", 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\nawsaccesskeyid = AK.."), 0644)
|
||||
os.Setenv("HOME", d)
|
||||
|
||||
_, err = aws.SharedAuth()
|
||||
c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in credentials file")
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuthDefaultCredentials(c *C) {
|
||||
os.Clearenv()
|
||||
|
||||
d, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
err = os.Mkdir(d+"/.aws", 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ioutil.WriteFile(d+"/.aws/credentials", []byte("[default]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
|
||||
os.Setenv("HOME", d)
|
||||
|
||||
auth, err := aws.SharedAuth()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||
}
|
||||
|
||||
func (s *S) TestSharedAuth(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_PROFILE", "bar")
|
||||
|
||||
d, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
err = os.Mkdir(d+"/.aws", 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
|
||||
os.Setenv("HOME", d)
|
||||
|
||||
auth, err := aws.SharedAuth()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthNoSecret(c *C) {
|
||||
os.Clearenv()
|
||||
_, err := aws.EnvAuth()
|
||||
@@ -52,6 +157,16 @@ func (s *S) TestEnvAuth(c *C) {
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthWithToken(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||
os.Setenv("AWS_SECURITY_TOKEN", "token")
|
||||
auth, err := aws.EnvAuth()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access", Token: "token"})
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthAlt(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_SECRET_KEY", "secret")
|
||||
|
||||
@@ -178,7 +178,7 @@ func buildError(r *http.Response) error {
|
||||
err.RequestId = errors.RequestId
|
||||
err.StatusCode = r.StatusCode
|
||||
if err.Message == "" {
|
||||
err.Message = r.Status
|
||||
err.Message = err.Code
|
||||
}
|
||||
return &err
|
||||
}
|
||||
@@ -220,6 +220,8 @@ func addBlockDeviceParams(prename string, params map[string]string, blockdevices
|
||||
}
|
||||
if k.DeleteOnTermination {
|
||||
params[prefix+"Ebs.DeleteOnTermination"] = "true"
|
||||
} else {
|
||||
params[prefix+"Ebs.DeleteOnTermination"] = "false"
|
||||
}
|
||||
if k.Encrypted {
|
||||
params[prefix+"Ebs.Encrypted"] = "true"
|
||||
@@ -253,6 +255,7 @@ type RunInstances struct {
|
||||
SubnetId string
|
||||
AssociatePublicIpAddress bool
|
||||
DisableAPITermination bool
|
||||
EbsOptimized bool
|
||||
ShutdownBehavior string
|
||||
PrivateIPAddress string
|
||||
BlockDevices []BlockDeviceMapping
|
||||
@@ -269,6 +272,15 @@ type RunInstancesResp struct {
|
||||
Instances []Instance `xml:"instancesSet>item"`
|
||||
}
|
||||
|
||||
// BlockDevice represents the association of a block device with an instance.
|
||||
type BlockDevice struct {
|
||||
DeviceName string `xml:"deviceName"`
|
||||
VolumeId string `xml:"ebs>volumeId"`
|
||||
Status string `xml:"ebs>status"`
|
||||
AttachTime string `xml:"ebs>attachTime"`
|
||||
DeleteOnTermination bool `xml:"ebs>deleteOnTermination"`
|
||||
}
|
||||
|
||||
// Instance encapsulates a running instance in EC2.
|
||||
//
|
||||
// See http://goo.gl/OCH8a for more details.
|
||||
@@ -296,6 +308,8 @@ type Instance struct {
|
||||
LaunchTime time.Time `xml:"launchTime"`
|
||||
SourceDestCheck bool `xml:"sourceDestCheck"`
|
||||
SecurityGroups []SecurityGroup `xml:"groupSet>item"`
|
||||
EbsOptimized string `xml:"ebsOptimized"`
|
||||
BlockDevices []BlockDevice `xml:"blockDeviceMapping>item"`
|
||||
}
|
||||
|
||||
// RunInstances starts new instances in EC2.
|
||||
@@ -392,6 +406,9 @@ func (ec2 *EC2) RunInstances(options *RunInstances) (resp *RunInstancesResp, err
|
||||
if options.DisableAPITermination {
|
||||
params["DisableApiTermination"] = "true"
|
||||
}
|
||||
if options.EbsOptimized {
|
||||
params["EbsOptimized"] = "true"
|
||||
}
|
||||
if options.ShutdownBehavior != "" {
|
||||
params["InstanceInitiatedShutdownBehavior"] = options.ShutdownBehavior
|
||||
}
|
||||
@@ -419,6 +436,78 @@ func clientToken() (string, error) {
|
||||
return hex.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Instance events and status functions and types.
|
||||
|
||||
// The DescribeInstanceStatus type encapsulates options for the respective request in EC2.
|
||||
//
|
||||
// See http://goo.gl/DFySJY for more details.
|
||||
type EventsSet struct {
|
||||
Code string `xml:"code"`
|
||||
Description string `xml:"description"`
|
||||
NotBefore string `xml:"notBefore"`
|
||||
NotAfter string `xml:"notAfter"`
|
||||
}
|
||||
|
||||
type StatusDetails struct {
|
||||
Name string `xml:"name"`
|
||||
Status string `xml:"status"`
|
||||
ImpairedSince string `xml:"impairedSince"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Status string `xml:"status"`
|
||||
Details []StatusDetails `xml:"details>item"`
|
||||
}
|
||||
|
||||
type InstanceStatusSet struct {
|
||||
InstanceId string `xml:"instanceId"`
|
||||
AvailabilityZone string `xml:"availabilityZone"`
|
||||
InstanceState InstanceState `xml:"instanceState"`
|
||||
SystemStatus Status `xml:"systemStatus"`
|
||||
InstanceStatus Status `xml:"instanceStatus"`
|
||||
Events []EventsSet `xml:"eventsSet>item"`
|
||||
}
|
||||
|
||||
type DescribeInstanceStatusResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
InstanceStatus []InstanceStatusSet `xml:"instanceStatusSet>item"`
|
||||
}
|
||||
|
||||
type DescribeInstanceStatus struct {
|
||||
InstanceIds []string
|
||||
IncludeAllInstances bool
|
||||
MaxResults int64
|
||||
NextToken string
|
||||
}
|
||||
|
||||
func (ec2 *EC2) DescribeInstanceStatus(options *DescribeInstanceStatus, filter *Filter) (resp *DescribeInstanceStatusResp, err error) {
|
||||
params := makeParams("DescribeInstanceStatus")
|
||||
if options.IncludeAllInstances {
|
||||
params["IncludeAllInstances"] = "true"
|
||||
}
|
||||
if len(options.InstanceIds) > 0 {
|
||||
addParamsList(params, "InstanceIds", options.InstanceIds)
|
||||
}
|
||||
if options.MaxResults > 0 {
|
||||
params["MaxResults"] = strconv.FormatInt(options.MaxResults, 10)
|
||||
}
|
||||
if options.NextToken != "" {
|
||||
params["NextToken"] = options.NextToken
|
||||
}
|
||||
if filter != nil {
|
||||
filter.addParams(params)
|
||||
}
|
||||
|
||||
resp = &DescribeInstanceStatusResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Spot Instance management functions and types.
|
||||
|
||||
@@ -478,6 +567,12 @@ type SpotLaunchSpec struct {
|
||||
BlockDevices []BlockDeviceMapping `xml:"blockDeviceMapping>item"`
|
||||
}
|
||||
|
||||
type SpotStatus struct {
|
||||
Code string `xml:"code"`
|
||||
UpdateTime string `xml:"updateTime"`
|
||||
Message string `xml:"message"`
|
||||
}
|
||||
|
||||
type SpotRequestResult struct {
|
||||
SpotRequestId string `xml:"spotInstanceRequestId"`
|
||||
SpotPrice string `xml:"spotPrice"`
|
||||
@@ -485,6 +580,7 @@ type SpotRequestResult struct {
|
||||
AvailZone string `xml:"launchedAvailabilityZone"`
|
||||
InstanceId string `xml:"instanceId"`
|
||||
State string `xml:"state"`
|
||||
Status SpotStatus `xml:"status"`
|
||||
SpotLaunchSpec SpotLaunchSpec `xml:"launchSpecification"`
|
||||
CreateTime string `xml:"createTime"`
|
||||
Tags []Tag `xml:"tagSet>item"`
|
||||
@@ -636,6 +732,61 @@ func (ec2 *EC2) CancelSpotRequests(spotrequestIds []string) (resp *CancelSpotReq
|
||||
return
|
||||
}
|
||||
|
||||
type DescribeSpotPriceHistory struct {
|
||||
InstanceType []string
|
||||
ProductDescription []string
|
||||
AvailabilityZone string
|
||||
StartTime, EndTime time.Time
|
||||
}
|
||||
|
||||
// Response to a DescribeSpotPriceHisotyr request.
|
||||
//
|
||||
// See http://goo.gl/3BKHj for more details.
|
||||
type DescribeSpotPriceHistoryResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
History []SpotPriceHistory `xml:"spotPriceHistorySet>item"`
|
||||
}
|
||||
|
||||
type SpotPriceHistory struct {
|
||||
InstanceType string `xml:"instanceType"`
|
||||
ProductDescription string `xml:"productDescription"`
|
||||
SpotPrice string `xml:"spotPrice"`
|
||||
Timestamp time.Time `xml:"timestamp"`
|
||||
AvailabilityZone string `xml:"availabilityZone"`
|
||||
}
|
||||
|
||||
// DescribeSpotPriceHistory gets the spot pricing history.
|
||||
//
|
||||
// See http://goo.gl/3BKHj for more details.
|
||||
func (ec2 *EC2) DescribeSpotPriceHistory(o *DescribeSpotPriceHistory) (resp *DescribeSpotPriceHistoryResp, err error) {
|
||||
params := makeParams("DescribeSpotPriceHistory")
|
||||
if o.AvailabilityZone != "" {
|
||||
params["AvailabilityZone"] = o.AvailabilityZone
|
||||
}
|
||||
|
||||
if !o.StartTime.IsZero() {
|
||||
params["StartTime"] = o.StartTime.In(time.UTC).Format(time.RFC3339)
|
||||
}
|
||||
if !o.EndTime.IsZero() {
|
||||
params["EndTime"] = o.EndTime.In(time.UTC).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if len(o.InstanceType) > 0 {
|
||||
addParamsList(params, "InstanceType", o.InstanceType)
|
||||
}
|
||||
if len(o.ProductDescription) > 0 {
|
||||
addParamsList(params, "ProductDescription", o.ProductDescription)
|
||||
}
|
||||
|
||||
resp = &DescribeSpotPriceHistoryResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Response to a TerminateInstances request.
|
||||
//
|
||||
// See http://goo.gl/3BKHj for more details.
|
||||
@@ -1834,6 +1985,29 @@ func (ec2 *EC2) CreateTags(resourceIds []string, tags []Tag) (resp *SimpleResp,
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type TagsResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
Tags []ResourceTag `xml:"tagSet>item"`
|
||||
}
|
||||
|
||||
type ResourceTag struct {
|
||||
Tag
|
||||
ResourceId string `xml:"resourceId"`
|
||||
ResourceType string `xml:"resourceType"`
|
||||
}
|
||||
|
||||
func (ec2 *EC2) Tags(filter *Filter) (*TagsResp, error) {
|
||||
params := makeParams("DescribeTags")
|
||||
filter.addParams(params)
|
||||
|
||||
resp := &TagsResp{}
|
||||
if err := ec2.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to a StartInstances request.
|
||||
//
|
||||
// See http://goo.gl/awKeF for more details.
|
||||
@@ -2006,6 +2180,27 @@ type CreateVpcResp struct {
|
||||
VPC VPC `xml:"vpc"`
|
||||
}
|
||||
|
||||
// The ModifyVpcAttribute request parameters.
|
||||
//
|
||||
// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
|
||||
type ModifyVpcAttribute struct {
|
||||
EnableDnsSupport bool
|
||||
EnableDnsHostnames bool
|
||||
|
||||
SetEnableDnsSupport bool
|
||||
SetEnableDnsHostnames bool
|
||||
}
|
||||
|
||||
// Response to a DescribeVpcAttribute request.
|
||||
//
|
||||
// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
|
||||
type VpcAttributeResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
VpcId string `xml:"vpcId"`
|
||||
EnableDnsSupport bool `xml:"enableDnsSupport>value"`
|
||||
EnableDnsHostnames bool `xml:"enableDnsHostnames>value"`
|
||||
}
|
||||
|
||||
// CreateInternetGateway request parameters.
|
||||
//
|
||||
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateInternetGateway.html
|
||||
@@ -2074,6 +2269,19 @@ type CreateSubnetResp struct {
|
||||
Subnet Subnet `xml:"subnet"`
|
||||
}
|
||||
|
||||
// The ModifySubnetAttribute request parameters
|
||||
//
|
||||
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html
|
||||
type ModifySubnetAttribute struct {
|
||||
SubnetId string
|
||||
MapPublicIpOnLaunch bool
|
||||
}
|
||||
|
||||
type ModifySubnetAttributeResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
Return bool `xml:"return"`
|
||||
}
|
||||
|
||||
// Response to a DescribeInternetGateways request.
|
||||
type InternetGatewaysResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
@@ -2208,6 +2416,49 @@ func (ec2 *EC2) DescribeVpcs(ids []string, filter *Filter) (resp *VpcsResp, err
|
||||
return
|
||||
}
|
||||
|
||||
// VpcAttribute describes an attribute of a VPC.
|
||||
// You can specify only one attribute at a time.
|
||||
// Valid attributes are:
|
||||
// enableDnsSupport | enableDnsHostnames
|
||||
//
|
||||
// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
|
||||
func (ec2 *EC2) VpcAttribute(vpcId, attribute string) (resp *VpcAttributeResp, err error) {
|
||||
params := makeParams("DescribeVpcAttribute")
|
||||
params["VpcId"] = vpcId
|
||||
params["Attribute"] = attribute
|
||||
|
||||
resp = &VpcAttributeResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ModifyVpcAttribute modifies the specified attribute of the specified VPC.
|
||||
//
|
||||
// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-ModifyVpcAttribute.html for more details.
|
||||
func (ec2 *EC2) ModifyVpcAttribute(vpcId string, options *ModifyVpcAttribute) (*SimpleResp, error) {
|
||||
params := makeParams("ModifyVpcAttribute")
|
||||
|
||||
params["VpcId"] = vpcId
|
||||
|
||||
if options.SetEnableDnsSupport {
|
||||
params["EnableDnsSupport.Value"] = strconv.FormatBool(options.EnableDnsSupport)
|
||||
}
|
||||
|
||||
if options.SetEnableDnsHostnames {
|
||||
params["EnableDnsHostnames.Value"] = strconv.FormatBool(options.EnableDnsHostnames)
|
||||
}
|
||||
|
||||
resp := &SimpleResp{}
|
||||
if err := ec2.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Create a new subnet.
|
||||
func (ec2 *EC2) CreateSubnet(options *CreateSubnet) (resp *CreateSubnetResp, err error) {
|
||||
params := makeParams("CreateSubnet")
|
||||
@@ -2237,6 +2488,26 @@ func (ec2 *EC2) DeleteSubnet(id string) (resp *SimpleResp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ModifySubnetAttribute
|
||||
//
|
||||
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html
|
||||
func (ec2 *EC2) ModifySubnetAttribute(options *ModifySubnetAttribute) (resp *ModifySubnetAttributeResp, err error) {
|
||||
params := makeParams("ModifySubnetAttribute")
|
||||
params["SubnetId"] = options.SubnetId
|
||||
if options.MapPublicIpOnLaunch {
|
||||
params["MapPublicIpOnLaunch.Value"] = "true"
|
||||
} else {
|
||||
params["MapPublicIpOnLaunch.Value"] = "false"
|
||||
}
|
||||
|
||||
resp = &ModifySubnetAttributeResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeSubnets
|
||||
//
|
||||
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSubnets.html
|
||||
|
||||
@@ -95,13 +95,13 @@ func (s *S) TestRunInstancesErrorWithoutXML(c *C) {
|
||||
testServer.WaitRequest()
|
||||
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, ErrorMatches, "500 Internal Server Error")
|
||||
c.Assert(err, ErrorMatches, "")
|
||||
|
||||
ec2err, ok := err.(*ec2.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(ec2err.StatusCode, Equals, 500)
|
||||
c.Assert(ec2err.Code, Equals, "")
|
||||
c.Assert(ec2err.Message, Equals, "500 Internal Server Error")
|
||||
c.Assert(ec2err.Message, Equals, "")
|
||||
c.Assert(ec2err.RequestId, Equals, "")
|
||||
}
|
||||
|
||||
@@ -114,13 +114,13 @@ func (s *S) TestRequestSpotInstancesErrorWithoutXML(c *C) {
|
||||
testServer.WaitRequest()
|
||||
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, ErrorMatches, "500 Internal Server Error")
|
||||
c.Assert(err, ErrorMatches, "")
|
||||
|
||||
ec2err, ok := err.(*ec2.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(ec2err.StatusCode, Equals, 500)
|
||||
c.Assert(ec2err.Code, Equals, "")
|
||||
c.Assert(ec2err.Message, Equals, "500 Internal Server Error")
|
||||
c.Assert(ec2err.Message, Equals, "")
|
||||
c.Assert(ec2err.RequestId, Equals, "")
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ func (s *S) TestRunInstancesExample(c *C) {
|
||||
Monitoring: true,
|
||||
SubnetId: "subnet-id",
|
||||
DisableAPITermination: true,
|
||||
EbsOptimized: true,
|
||||
ShutdownBehavior: "terminate",
|
||||
PrivateIPAddress: "10.0.0.25",
|
||||
BlockDevices: []ec2.BlockDeviceMapping{
|
||||
@@ -168,6 +169,7 @@ func (s *S) TestRunInstancesExample(c *C) {
|
||||
c.Assert(req.Form["Monitoring.Enabled"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"})
|
||||
c.Assert(req.Form["DisableApiTermination"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["EbsOptimized"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], DeepEquals, []string{"terminate"})
|
||||
c.Assert(req.Form["PrivateIpAddress"], DeepEquals, []string{"10.0.0.25"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sdb"})
|
||||
@@ -266,6 +268,9 @@ func (s *S) TestRequestSpotInstancesExample(c *C) {
|
||||
c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5")
|
||||
c.Assert(resp.SpotRequestResults[0].State, Equals, "open")
|
||||
c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "pending-evaluation")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request has been submitted for review, and is pending evaluation.")
|
||||
}
|
||||
|
||||
func (s *S) TestCancelSpotRequestsExample(c *C) {
|
||||
@@ -301,6 +306,7 @@ func (s *S) TestTerminateInstancesExample(c *C) {
|
||||
c.Assert(req.Form["Monitoring.Enabled"], IsNil)
|
||||
c.Assert(req.Form["SubnetId"], IsNil)
|
||||
c.Assert(req.Form["DisableApiTermination"], IsNil)
|
||||
c.Assert(req.Form["EbsOptimized"], IsNil)
|
||||
c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], IsNil)
|
||||
c.Assert(req.Form["PrivateIpAddress"], IsNil)
|
||||
|
||||
@@ -334,6 +340,9 @@ func (s *S) TestDescribeSpotRequestsExample(c *C) {
|
||||
c.Assert(resp.SpotRequestResults[0].State, Equals, "active")
|
||||
c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5")
|
||||
c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "fulfilled")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z")
|
||||
c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request is fulfilled.")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstancesExample1(c *C) {
|
||||
@@ -366,6 +375,13 @@ func (s *S) TestDescribeInstancesExample1(c *C) {
|
||||
c.Assert(r0i.PrivateDNSName, Equals, "domU-12-31-39-10-56-34.compute-1.internal")
|
||||
c.Assert(r0i.DNSName, Equals, "ec2-174-129-165-232.compute-1.amazonaws.com")
|
||||
c.Assert(r0i.AvailZone, Equals, "us-east-1b")
|
||||
|
||||
b0 := r0i.BlockDevices[0]
|
||||
c.Assert(b0.DeviceName, Equals, "/dev/sda1")
|
||||
c.Assert(b0.VolumeId, Equals, "vol-a082c1c9")
|
||||
c.Assert(b0.Status, Equals, "attached")
|
||||
c.Assert(b0.AttachTime, Equals, "2010-08-17T01:15:21.000Z")
|
||||
c.Assert(b0.DeleteOnTermination, Equals, false)
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstancesExample2(c *C) {
|
||||
@@ -1041,6 +1057,31 @@ func (s *S) TestSignatureWithEndpointPath(c *C) {
|
||||
c.Assert(req.Form["Signature"], DeepEquals, []string{"QmvgkYGn19WirCuCz/jRp3RmRgFwWR5WRkKZ5AZnyXQ="})
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstanceStatusExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeInstanceStatusExample)
|
||||
options := &ec2.DescribeInstanceStatus{}
|
||||
resp, err := s.ec2.DescribeInstanceStatus(options, nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstanceStatus"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE")
|
||||
c.Assert(resp.InstanceStatus[0].InstanceId, Equals, "i-1a2b3c4d")
|
||||
c.Assert(resp.InstanceStatus[0].InstanceState.Code, Equals, 16)
|
||||
c.Assert(resp.InstanceStatus[0].SystemStatus.Status, Equals, "impaired")
|
||||
c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Name, Equals, "reachability")
|
||||
c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Status, Equals, "failed")
|
||||
c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z")
|
||||
c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Name, Equals, "reachability")
|
||||
c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Status, Equals, "failed")
|
||||
c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z")
|
||||
c.Assert(resp.InstanceStatus[0].Events[0].Code, Equals, "instance-retirement")
|
||||
c.Assert(resp.InstanceStatus[0].Events[0].Description, Equals, "The instance is running on degraded hardware")
|
||||
c.Assert(resp.InstanceStatus[0].Events[0].NotBefore, Equals, "YYYY-MM-DDTHH:MM:SS+0000")
|
||||
c.Assert(resp.InstanceStatus[0].Events[0].NotAfter, Equals, "YYYY-MM-DDTHH:MM:SS+0000")
|
||||
}
|
||||
|
||||
func (s *S) TestAllocateAddressExample(c *C) {
|
||||
testServer.Response(200, nil, AllocateAddressExample)
|
||||
|
||||
@@ -1223,6 +1264,24 @@ func (s *S) TestCreateSubnet(c *C) {
|
||||
c.Assert(resp.Subnet.AvailableIpAddressCount, Equals, 251)
|
||||
}
|
||||
|
||||
func (s *S) TestModifySubnetAttribute(c *C) {
|
||||
testServer.Response(200, nil, ModifySubnetAttributeExample)
|
||||
|
||||
options := &ec2.ModifySubnetAttribute{
|
||||
SubnetId: "foo",
|
||||
MapPublicIpOnLaunch: true,
|
||||
}
|
||||
|
||||
resp, err := s.ec2.ModifySubnetAttribute(options)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["SubnetId"], DeepEquals, []string{"foo"})
|
||||
c.Assert(req.Form["MapPublicIpOnLaunch.Value"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestResetImageAttribute(c *C) {
|
||||
testServer.Response(200, nil, ResetImageAttributeExample)
|
||||
|
||||
|
||||
+141
-2
@@ -108,7 +108,7 @@ var RequestSpotInstancesExample = `
|
||||
<state>open</state>
|
||||
<status>
|
||||
<code>pending-evaluation</code>
|
||||
<updateTime>YYYY-MM-DDTHH:MM:SS.000Z</updateTime>
|
||||
<updateTime>2008-05-07T12:51:50.000Z</updateTime>
|
||||
<message>Your Spot request has been submitted for review, and is pending evaluation.</message>
|
||||
</status>
|
||||
<availabilityZoneGroup>MyAzGroup</availabilityZoneGroup>
|
||||
@@ -147,7 +147,7 @@ var DescribeSpotRequestsExample = `
|
||||
<state>active</state>
|
||||
<status>
|
||||
<code>fulfilled</code>
|
||||
<updateTime>YYYY-MM-DDTHH:MM:SS.000Z</updateTime>
|
||||
<updateTime>2008-05-07T12:51:50.000Z</updateTime>
|
||||
<message>Your Spot request is fulfilled.</message>
|
||||
</status>
|
||||
<launchSpecification>
|
||||
@@ -766,6 +766,137 @@ var AllocateAddressExample = `
|
||||
</AllocateAddressResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/DFySJY
|
||||
var DescribeInstanceStatusExample = `
|
||||
<DescribeInstanceStatusResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||
<requestId>3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE</requestId>
|
||||
<instanceStatusSet>
|
||||
<item>
|
||||
<instanceId>i-1a2b3c4d</instanceId>
|
||||
<availabilityZone>us-east-1d</availabilityZone>
|
||||
<instanceState>
|
||||
<code>16</code>
|
||||
<name>running</name>
|
||||
</instanceState>
|
||||
<systemStatus>
|
||||
<status>impaired</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>failed</status>
|
||||
<impairedSince>YYYY-MM-DDTHH:MM:SS.000Z</impairedSince>
|
||||
</item>
|
||||
</details>
|
||||
</systemStatus>
|
||||
<instanceStatus>
|
||||
<status>impaired</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>failed</status>
|
||||
<impairedSince>YYYY-MM-DDTHH:MM:SS.000Z</impairedSince>
|
||||
</item>
|
||||
</details>
|
||||
</instanceStatus>
|
||||
<eventsSet>
|
||||
<item>
|
||||
<code>instance-retirement</code>
|
||||
<description>The instance is running on degraded hardware</description>
|
||||
<notBefore>YYYY-MM-DDTHH:MM:SS+0000</notBefore>
|
||||
<notAfter>YYYY-MM-DDTHH:MM:SS+0000</notAfter>
|
||||
</item>
|
||||
</eventsSet>
|
||||
</item>
|
||||
<item>
|
||||
<instanceId>i-2a2b3c4d</instanceId>
|
||||
<availabilityZone>us-east-1d</availabilityZone>
|
||||
<instanceState>
|
||||
<code>16</code>
|
||||
<name>running</name>
|
||||
</instanceState>
|
||||
<systemStatus>
|
||||
<status>ok</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>passed</status>
|
||||
</item>
|
||||
</details>
|
||||
</systemStatus>
|
||||
<instanceStatus>
|
||||
<status>ok</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>passed</status>
|
||||
</item>
|
||||
</details>
|
||||
</instanceStatus>
|
||||
<eventsSet>
|
||||
<item>
|
||||
<code>instance-reboot</code>
|
||||
<description>The instance is scheduled for a reboot</description>
|
||||
<notBefore>YYYY-MM-DDTHH:MM:SS+0000</notBefore>
|
||||
<notAfter>YYYY-MM-DDTHH:MM:SS+0000</notAfter>
|
||||
</item>
|
||||
</eventsSet>
|
||||
</item>
|
||||
<item>
|
||||
<instanceId>i-3a2b3c4d</instanceId>
|
||||
<availabilityZone>us-east-1c</availabilityZone>
|
||||
<instanceState>
|
||||
<code>16</code>
|
||||
<name>running</name>
|
||||
</instanceState>
|
||||
<systemStatus>
|
||||
<status>ok</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>passed</status>
|
||||
</item>
|
||||
</details>
|
||||
</systemStatus>
|
||||
<instanceStatus>
|
||||
<status>ok</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>passed</status>
|
||||
</item>
|
||||
</details>
|
||||
</instanceStatus>
|
||||
</item>
|
||||
<item>
|
||||
<instanceId>i-4a2b3c4d</instanceId>
|
||||
<availabilityZone>us-east-1c</availabilityZone>
|
||||
<instanceState>
|
||||
<code>16</code>
|
||||
<name>running</name>
|
||||
</instanceState>
|
||||
<systemStatus>
|
||||
<status>ok</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>passed</status>
|
||||
</item>
|
||||
</details>
|
||||
</systemStatus>
|
||||
<instanceStatus>
|
||||
<status>insufficient-data</status>
|
||||
<details>
|
||||
<item>
|
||||
<name>reachability</name>
|
||||
<status>insufficient-data</status>
|
||||
</item>
|
||||
</details>
|
||||
</instanceStatus>
|
||||
</item>
|
||||
</instanceStatusSet>
|
||||
</DescribeInstanceStatusResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/3Q0oCc
|
||||
var ReleaseAddressExample = `
|
||||
<ReleaseAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
@@ -845,6 +976,14 @@ var CreateSubnetExample = `
|
||||
</CreateSubnetResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/tu2Kxm
|
||||
var ModifySubnetAttributeExample = `
|
||||
<ModifySubnetAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<return>true</return>
|
||||
</ModifySubnetAttributeResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/r6ZCPm
|
||||
var ResetImageAttributeExample = `
|
||||
<ResetImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||
|
||||
@@ -93,21 +93,28 @@ func makeParams(action string) map[string]string {
|
||||
type Listener struct {
|
||||
InstancePort int64 `xml:"member>Listener>InstancePort"`
|
||||
InstanceProtocol string `xml:"member>Listener>InstanceProtocol"`
|
||||
SSLCertificateId string `xml:"member>Listener>SSLCertificateId"`
|
||||
LoadBalancerPort int64 `xml:"member>Listener>LoadBalancerPort"`
|
||||
Protocol string `xml:"member>Listener>Protocol"`
|
||||
}
|
||||
|
||||
// An Instance attaches to an elb
|
||||
type Instance struct {
|
||||
InstanceId string `xml:"member>InstanceId"`
|
||||
InstanceId string `xml:"InstanceId"`
|
||||
}
|
||||
|
||||
// A tag attached to an elb
|
||||
type Tag struct {
|
||||
Key string `xml:"Key"`
|
||||
Value string `xml:"Value"`
|
||||
}
|
||||
|
||||
// An InstanceState from an elb health query
|
||||
type InstanceState struct {
|
||||
InstanceId string `xml:"member>InstanceId"`
|
||||
Description string `xml:"member>Description"`
|
||||
State string `xml:"member>State"`
|
||||
ReasonCode string `xml:"member>ReasonCode"`
|
||||
InstanceId string `xml:"InstanceId"`
|
||||
Description string `xml:"Description"`
|
||||
State string `xml:"State"`
|
||||
ReasonCode string `xml:"ReasonCode"`
|
||||
}
|
||||
|
||||
// An Instance attaches to an elb
|
||||
@@ -115,6 +122,67 @@ type AvailabilityZone struct {
|
||||
AvailabilityZone string `xml:"member"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// AddTags
|
||||
|
||||
type AddTags struct {
|
||||
LoadBalancerNames []string
|
||||
Tags []Tag
|
||||
}
|
||||
|
||||
type AddTagsResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
func (elb *ELB) AddTags(options *AddTags) (resp *AddTagsResp, err error) {
|
||||
params := makeParams("AddTags")
|
||||
|
||||
for i, v := range options.LoadBalancerNames {
|
||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
for i, v := range options.Tags {
|
||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key
|
||||
params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value
|
||||
}
|
||||
|
||||
resp = &AddTagsResp{}
|
||||
|
||||
err = elb.query(params, resp)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// RemoveTags
|
||||
|
||||
type RemoveTags struct {
|
||||
LoadBalancerNames []string
|
||||
TagKeys []string
|
||||
}
|
||||
|
||||
type RemoveTagsResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
func (elb *ELB) RemoveTags(options *RemoveTags) (resp *RemoveTagsResp, err error) {
|
||||
params := makeParams("RemoveTags")
|
||||
|
||||
for i, v := range options.LoadBalancerNames {
|
||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
for i, v := range options.TagKeys {
|
||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v
|
||||
}
|
||||
|
||||
resp = &RemoveTagsResp{}
|
||||
|
||||
err = elb.query(params, resp)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Create
|
||||
|
||||
@@ -126,6 +194,7 @@ type CreateLoadBalancer struct {
|
||||
Internal bool // true for vpc elbs
|
||||
SecurityGroups []string
|
||||
Subnets []string
|
||||
Tags []Tag
|
||||
}
|
||||
|
||||
type CreateLoadBalancerResp struct {
|
||||
@@ -155,6 +224,12 @@ func (elb *ELB) CreateLoadBalancer(options *CreateLoadBalancer) (resp *CreateLoa
|
||||
params["Listeners.member."+strconv.Itoa(i+1)+".InstancePort"] = strconv.FormatInt(v.InstancePort, 10)
|
||||
params["Listeners.member."+strconv.Itoa(i+1)+".Protocol"] = v.Protocol
|
||||
params["Listeners.member."+strconv.Itoa(i+1)+".InstanceProtocol"] = v.InstanceProtocol
|
||||
params["Listeners.member."+strconv.Itoa(i+1)+".SSLCertificateId"] = v.SSLCertificateId
|
||||
}
|
||||
|
||||
for i, v := range options.Tags {
|
||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key
|
||||
params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value
|
||||
}
|
||||
|
||||
if options.Internal {
|
||||
@@ -203,10 +278,13 @@ func (elb *ELB) DeleteLoadBalancer(options *DeleteLoadBalancer) (resp *SimpleRes
|
||||
type LoadBalancer struct {
|
||||
LoadBalancerName string `xml:"member>LoadBalancerName"`
|
||||
Listeners []Listener `xml:"member>ListenerDescriptions"`
|
||||
Instances []Instance `xml:"member>Instances"`
|
||||
Instances []Instance `xml:"member>Instances>member"`
|
||||
HealthCheck HealthCheck `xml:"member>HealthCheck"`
|
||||
AvailabilityZones []AvailabilityZone `xml:"member>AvailabilityZones"`
|
||||
Scheme string `xml:"member>Scheme"`
|
||||
DNSName string `xml:"member>DNSName"`
|
||||
SecurityGroups []string `xml:"member>SecurityGroups>member"`
|
||||
Scheme string `xml:"member>Scheme"`
|
||||
Subnets []string `xml:"member>Subnets>member"`
|
||||
}
|
||||
|
||||
// DescribeLoadBalancer request params
|
||||
@@ -302,6 +380,84 @@ func (elb *ELB) DeregisterInstancesFromLoadBalancer(options *DeregisterInstances
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DescribeTags
|
||||
|
||||
type DescribeTags struct {
|
||||
LoadBalancerNames []string
|
||||
}
|
||||
|
||||
type LoadBalancerTag struct {
|
||||
Tags []Tag `xml:"Tags>member"`
|
||||
LoadBalancerName string `xml:"LoadBalancerName"`
|
||||
}
|
||||
|
||||
type DescribeTagsResp struct {
|
||||
LoadBalancerTags []LoadBalancerTag `xml:"DescribeTagsResult>TagDescriptions>member"`
|
||||
NextToken string `xml:"DescribeTagsResult>NextToken"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
func (elb *ELB) DescribeTags(options *DescribeTags) (resp *DescribeTagsResp, err error) {
|
||||
params := makeParams("DescribeTags")
|
||||
|
||||
for i, v := range options.LoadBalancerNames {
|
||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
||||
}
|
||||
|
||||
resp = &DescribeTagsResp{}
|
||||
|
||||
err = elb.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Health Checks
|
||||
|
||||
type HealthCheck struct {
|
||||
HealthyThreshold int64 `xml:"HealthyThreshold"`
|
||||
UnhealthyThreshold int64 `xml:"UnhealthyThreshold"`
|
||||
Interval int64 `xml:"Interval"`
|
||||
Target string `xml:"Target"`
|
||||
Timeout int64 `xml:"Timeout"`
|
||||
}
|
||||
|
||||
type ConfigureHealthCheck struct {
|
||||
LoadBalancerName string
|
||||
Check HealthCheck
|
||||
}
|
||||
|
||||
type ConfigureHealthCheckResp struct {
|
||||
Check HealthCheck `xml:"ConfigureHealthCheckResult>HealthCheck"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
func (elb *ELB) ConfigureHealthCheck(options *ConfigureHealthCheck) (resp *ConfigureHealthCheckResp, err error) {
|
||||
params := makeParams("ConfigureHealthCheck")
|
||||
|
||||
params["LoadBalancerName"] = options.LoadBalancerName
|
||||
params["HealthCheck.HealthyThreshold"] = strconv.Itoa(int(options.Check.HealthyThreshold))
|
||||
params["HealthCheck.UnhealthyThreshold"] = strconv.Itoa(int(options.Check.UnhealthyThreshold))
|
||||
params["HealthCheck.Interval"] = strconv.Itoa(int(options.Check.Interval))
|
||||
params["HealthCheck.Target"] = options.Check.Target
|
||||
params["HealthCheck.Timeout"] = strconv.Itoa(int(options.Check.Timeout))
|
||||
|
||||
resp = &ConfigureHealthCheckResp{}
|
||||
|
||||
err = elb.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Instance Health
|
||||
|
||||
@@ -311,7 +467,7 @@ type DescribeInstanceHealth struct {
|
||||
}
|
||||
|
||||
type DescribeInstanceHealthResp struct {
|
||||
InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates"`
|
||||
InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates>member"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,48 @@ func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestAddTags(c *C) {
|
||||
testServer.Response(200, nil, AddTagsExample)
|
||||
|
||||
options := elb.AddTags{
|
||||
LoadBalancerNames: []string{"foobar"},
|
||||
Tags: []elb.Tag{
|
||||
{
|
||||
Key: "hello",
|
||||
Value: "world",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.elb.AddTags(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AddTags"})
|
||||
c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"})
|
||||
c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"})
|
||||
c.Assert(req.Form["Tags.member.1.Value"], DeepEquals, []string{"world"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "360e81f7-1100-11e4-b6ed-0f30EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestRemoveTags(c *C) {
|
||||
testServer.Response(200, nil, RemoveTagsExample)
|
||||
|
||||
options := elb.RemoveTags{
|
||||
LoadBalancerNames: []string{"foobar"},
|
||||
TagKeys: []string{"hello"},
|
||||
}
|
||||
|
||||
resp, err := s.elb.RemoveTags(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RemoveTags"})
|
||||
c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"})
|
||||
c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestCreateLoadBalancer(c *C) {
|
||||
testServer.Response(200, nil, CreateLoadBalancerExample)
|
||||
|
||||
@@ -38,6 +80,7 @@ func (s *S) TestCreateLoadBalancer(c *C) {
|
||||
Listeners: []elb.Listener{elb.Listener{
|
||||
InstancePort: 80,
|
||||
InstanceProtocol: "http",
|
||||
SSLCertificateId: "needToAddASSLCertToYourAWSAccount",
|
||||
LoadBalancerPort: 80,
|
||||
Protocol: "http",
|
||||
},
|
||||
@@ -92,6 +135,11 @@ func (s *S) TestDescribeLoadBalancers(c *C) {
|
||||
c.Assert(resp.LoadBalancers[0].AvailabilityZones[0].AvailabilityZone, Equals, "us-east-1a")
|
||||
c.Assert(resp.LoadBalancers[0].Scheme, Equals, "internet-facing")
|
||||
c.Assert(resp.LoadBalancers[0].DNSName, Equals, "MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com")
|
||||
c.Assert(resp.LoadBalancers[0].HealthCheck.HealthyThreshold, Equals, int64(2))
|
||||
c.Assert(resp.LoadBalancers[0].HealthCheck.UnhealthyThreshold, Equals, int64(10))
|
||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Interval, Equals, int64(90))
|
||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Target, Equals, "HTTP:80/")
|
||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Timeout, Equals, int64(60))
|
||||
}
|
||||
|
||||
func (s *S) TestRegisterInstancesWithLoadBalancer(c *C) {
|
||||
@@ -136,6 +184,35 @@ func (s *S) TestDeregisterInstancesFromLoadBalancer(c *C) {
|
||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestConfigureHealthCheck(c *C) {
|
||||
testServer.Response(200, nil, ConfigureHealthCheckExample)
|
||||
|
||||
options := elb.ConfigureHealthCheck{
|
||||
LoadBalancerName: "foobar",
|
||||
Check: elb.HealthCheck{
|
||||
HealthyThreshold: 2,
|
||||
UnhealthyThreshold: 2,
|
||||
Interval: 30,
|
||||
Target: "HTTP:80/ping",
|
||||
Timeout: 3,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.elb.ConfigureHealthCheck(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"ConfigureHealthCheck"})
|
||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(resp.Check.HealthyThreshold, Equals, int64(2))
|
||||
c.Assert(resp.Check.UnhealthyThreshold, Equals, int64(2))
|
||||
c.Assert(resp.Check.Interval, Equals, int64(30))
|
||||
c.Assert(resp.Check.Target, Equals, "HTTP:80/ping")
|
||||
c.Assert(resp.Check.Timeout, Equals, int64(3))
|
||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstanceHealth(c *C) {
|
||||
testServer.Response(200, nil, DescribeInstanceHealthExample)
|
||||
|
||||
@@ -152,5 +229,7 @@ func (s *S) TestDescribeInstanceHealth(c *C) {
|
||||
|
||||
c.Assert(resp.InstanceStates[0].InstanceId, Equals, "i-90d8c2a5")
|
||||
c.Assert(resp.InstanceStates[0].State, Equals, "InService")
|
||||
c.Assert(resp.InstanceStates[1].InstanceId, Equals, "i-06ea3e60")
|
||||
c.Assert(resp.InstanceStates[1].State, Equals, "OutOfService")
|
||||
c.Assert(resp.RequestId, Equals, "1549581b-12b7-11e3-895e-1334aEXAMPLE")
|
||||
}
|
||||
|
||||
+44
@@ -7,6 +7,26 @@ var ErrorDump = `
|
||||
</Error></Errors><RequestID>0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4</RequestID></Response>
|
||||
`
|
||||
|
||||
// http://goo.gl/OkMdtJ
|
||||
var AddTagsExample = `
|
||||
<AddTagsResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||
<AddTagsResult/>
|
||||
<ResponseMetadata>
|
||||
<RequestId>360e81f7-1100-11e4-b6ed-0f30EXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AddTagsResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/nT2E89
|
||||
var RemoveTagsExample = `
|
||||
<RemoveTagsResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||
<RemoveTagsResult/>
|
||||
<ResponseMetadata>
|
||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</RemoveTagsResponse>
|
||||
`
|
||||
|
||||
// http://goo.gl/gQRD2H
|
||||
var CreateLoadBalancerExample = `
|
||||
<CreateLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||
@@ -51,6 +71,7 @@ var DescribeLoadBalancersExample = `
|
||||
<Protocol>HTTP</Protocol>
|
||||
<LoadBalancerPort>80</LoadBalancerPort>
|
||||
<InstanceProtocol>HTTP</InstanceProtocol>
|
||||
<SSLCertificateId>needToAddASSLCertToYourAWSAccount</SSLCertificateId>
|
||||
<InstancePort>80</InstancePort>
|
||||
</Listener>
|
||||
</member>
|
||||
@@ -119,6 +140,23 @@ var DeregisterInstancesFromLoadBalancerExample = `
|
||||
</DeregisterInstancesFromLoadBalancerResponse>
|
||||
`
|
||||
|
||||
// http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/API_ConfigureHealthCheck.html
|
||||
var ConfigureHealthCheckExample = `
|
||||
<ConfigureHealthCheckResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||
<ConfigureHealthCheckResult>
|
||||
<HealthCheck>
|
||||
<Interval>30</Interval>
|
||||
<Target>HTTP:80/ping</Target>
|
||||
<HealthyThreshold>2</HealthyThreshold>
|
||||
<Timeout>3</Timeout>
|
||||
<UnhealthyThreshold>2</UnhealthyThreshold>
|
||||
</HealthCheck>
|
||||
</ConfigureHealthCheckResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ConfigureHealthCheckResponse>`
|
||||
|
||||
// http://goo.gl/cGNxfj
|
||||
var DescribeInstanceHealthExample = `
|
||||
<DescribeInstanceHealthResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
||||
@@ -130,6 +168,12 @@ var DescribeInstanceHealthExample = `
|
||||
<State>InService</State>
|
||||
<ReasonCode>N/A</ReasonCode>
|
||||
</member>
|
||||
<member>
|
||||
<Description>N/A</Description>
|
||||
<InstanceId>i-06ea3e60</InstanceId>
|
||||
<State>OutOfService</State>
|
||||
<ReasonCode>N/A</ReasonCode>
|
||||
</member>
|
||||
</InstanceStates>
|
||||
</DescribeInstanceHealthResult>
|
||||
<ResponseMetadata>
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
@@ -31,7 +32,7 @@ func sign(auth aws.Auth, method, path string, params url.Values, headers http.He
|
||||
params["SignatureVersion"] = []string{"2"}
|
||||
params["SignatureMethod"] = []string{"HmacSHA256"}
|
||||
if auth.Token != "" {
|
||||
params["SecurityToken"] = auth.Token
|
||||
params["SecurityToken"] = []string{auth.Token}
|
||||
}
|
||||
|
||||
// join up all the incoming params
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package sns
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Permission struct {
|
||||
ActionName string
|
||||
AccountId string
|
||||
}
|
||||
|
||||
type AddPermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// AddPermission
|
||||
//
|
||||
// See http://goo.gl/mbY4a for more details.
|
||||
func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err error) {
|
||||
resp = &AddPermissionResponse{}
|
||||
params := makeParams("AddPermission")
|
||||
|
||||
for i, p := range permissions {
|
||||
params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId
|
||||
params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName
|
||||
}
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type RemovePermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// RemovePermission
|
||||
//
|
||||
// See http://goo.gl/wGl5j for more details.
|
||||
func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err error) {
|
||||
resp = &RemovePermissionResponse{}
|
||||
params := makeParams("RemovePermission")
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
@@ -13,24 +13,16 @@
|
||||
// first-class package in goamz.
|
||||
package sns
|
||||
|
||||
// BUG(niemeyer): Package needs significant clean up.
|
||||
|
||||
// BUG(niemeyer): Topic values in responses are not being initialized
|
||||
// properly, since they're supposed to reference *SNS.
|
||||
|
||||
// BUG(niemeyer): Package needs documentation.
|
||||
|
||||
// BUG(niemeyer): Message.Message should be "Payload []byte"
|
||||
|
||||
// BUG(niemeyer): Message.SNS must be dropped.
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -41,32 +33,9 @@ type SNS struct {
|
||||
private byte // Reserve the right of using private data.
|
||||
}
|
||||
|
||||
type Topic struct {
|
||||
SNS *SNS
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
func New(auth aws.Auth, region aws.Region) *SNS {
|
||||
return &SNS{auth, region, 0}
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
SNS *SNS
|
||||
Topic *Topic
|
||||
Message [8192]byte
|
||||
Subject string
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
Endpoint string
|
||||
Owner string
|
||||
Protocol string
|
||||
SubscriptionArn string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
func (topic *Topic) Message(message [8192]byte, subject string) *Message {
|
||||
return &Message{topic.SNS, topic, message, subject}
|
||||
type AttributeEntry struct {
|
||||
Key string `xml:"key"`
|
||||
Value string `xml:"value"`
|
||||
}
|
||||
|
||||
type ResponseMetadata struct {
|
||||
@@ -74,35 +43,8 @@ type ResponseMetadata struct {
|
||||
BoxUsage float64 `xml:"ResponseMetadata>BoxUsage"`
|
||||
}
|
||||
|
||||
type ListTopicsResp struct {
|
||||
Topics []Topic `xml:"ListTopicsResult>Topics>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type CreateTopicResp struct {
|
||||
Topic Topic `xml:"CreateTopicResult"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type DeleteTopicResp struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ListSubscriptionsResp struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type AttributeEntry struct {
|
||||
Key string `xml:"key"`
|
||||
Value string `xml:"value"`
|
||||
}
|
||||
|
||||
type GetTopicAttributesResp struct {
|
||||
Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`
|
||||
ResponseMetadata
|
||||
func New(auth aws.Auth, region aws.Region) *SNS {
|
||||
return &SNS{auth, region, 0}
|
||||
}
|
||||
|
||||
func makeParams(action string) map[string]string {
|
||||
@@ -111,273 +53,6 @@ func makeParams(action string) map[string]string {
|
||||
return params
|
||||
}
|
||||
|
||||
// ListTopics
|
||||
//
|
||||
// See http://goo.gl/lfrMK for more details.
|
||||
func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResp, err error) {
|
||||
resp = &ListTopicsResp{}
|
||||
params := makeParams("ListTopics")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTopic
|
||||
//
|
||||
// See http://goo.gl/m9aAt for more details.
|
||||
func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResp, err error) {
|
||||
resp = &CreateTopicResp{}
|
||||
params := makeParams("CreateTopic")
|
||||
params["Name"] = Name
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteTopic
|
||||
//
|
||||
// See http://goo.gl/OXNcY for more details.
|
||||
func (sns *SNS) DeleteTopic(topic Topic) (resp *DeleteTopicResp, err error) {
|
||||
resp = &DeleteTopicResp{}
|
||||
params := makeParams("DeleteTopic")
|
||||
params["TopicArn"] = topic.TopicArn
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete
|
||||
//
|
||||
// Helper function for deleting a topic
|
||||
func (topic *Topic) Delete() (resp *DeleteTopicResp, err error) {
|
||||
return topic.SNS.DeleteTopic(*topic)
|
||||
}
|
||||
|
||||
// ListSubscriptions
|
||||
//
|
||||
// See http://goo.gl/k3aGn for more details.
|
||||
func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResp, err error) {
|
||||
resp = &ListSubscriptionsResp{}
|
||||
params := makeParams("ListSubscriptions")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/WXRoX for more details.
|
||||
func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResp, err error) {
|
||||
resp = &GetTopicAttributesResp{}
|
||||
params := makeParams("GetTopicAttributes")
|
||||
params["TopicArn"] = TopicArn
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type PublishOpt struct {
|
||||
Message string
|
||||
MessageStructure string
|
||||
Subject string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
type PublishResp struct {
|
||||
MessageId string `xml:"PublishResult>MessageId"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Publish
|
||||
//
|
||||
// See http://goo.gl/AY2D8 for more details.
|
||||
func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResp, err error) {
|
||||
resp = &PublishResp{}
|
||||
params := makeParams("Publish")
|
||||
|
||||
if options.Subject != "" {
|
||||
params["Subject"] = options.Subject
|
||||
}
|
||||
|
||||
if options.MessageStructure != "" {
|
||||
params["MessageStructure"] = options.MessageStructure
|
||||
}
|
||||
|
||||
if options.Message != "" {
|
||||
params["Message"] = options.Message
|
||||
}
|
||||
|
||||
if options.TopicArn != "" {
|
||||
params["TopicArn"] = options.TopicArn
|
||||
}
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type SetTopicAttributesResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// SetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/oVYW7 for more details.
|
||||
func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err error) {
|
||||
resp = &SetTopicAttributesResponse{}
|
||||
params := makeParams("SetTopicAttributes")
|
||||
|
||||
if AttributeName == "" || TopicArn == "" {
|
||||
return nil, errors.New("Invalid Attribute Name or TopicArn")
|
||||
}
|
||||
|
||||
params["AttributeName"] = AttributeName
|
||||
params["AttributeValue"] = AttributeValue
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type SubscribeResponse struct {
|
||||
SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Subscribe
|
||||
//
|
||||
// See http://goo.gl/c3iGS for more details.
|
||||
func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err error) {
|
||||
resp = &SubscribeResponse{}
|
||||
params := makeParams("Subscribe")
|
||||
|
||||
params["Endpoint"] = Endpoint
|
||||
params["Protocol"] = Protocol
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type UnsubscribeResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
//
|
||||
// See http://goo.gl/4l5Ge for more details.
|
||||
func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err error) {
|
||||
resp = &UnsubscribeResponse{}
|
||||
params := makeParams("Unsubscribe")
|
||||
|
||||
params["SubscriptionArn"] = SubscriptionArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionResponse struct {
|
||||
SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionOpt struct {
|
||||
AuthenticateOnUnsubscribe string
|
||||
Token string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
// ConfirmSubscription
|
||||
//
|
||||
// See http://goo.gl/3hXzH for more details.
|
||||
func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err error) {
|
||||
resp = &ConfirmSubscriptionResponse{}
|
||||
params := makeParams("ConfirmSubscription")
|
||||
|
||||
if options.AuthenticateOnUnsubscribe != "" {
|
||||
params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe
|
||||
}
|
||||
|
||||
params["Token"] = options.Token
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ActionName string
|
||||
AccountId string
|
||||
}
|
||||
|
||||
type AddPermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// AddPermission
|
||||
//
|
||||
// See http://goo.gl/mbY4a for more details.
|
||||
func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err error) {
|
||||
resp = &AddPermissionResponse{}
|
||||
params := makeParams("AddPermission")
|
||||
|
||||
for i, p := range permissions {
|
||||
params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId
|
||||
params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName
|
||||
}
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type RemovePermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// RemovePermission
|
||||
//
|
||||
// See http://goo.gl/wGl5j for more details.
|
||||
func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err error) {
|
||||
resp = &RemovePermissionResponse{}
|
||||
params := makeParams("RemovePermission")
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicResponse struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicOpt struct {
|
||||
NextToken string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
// ListSubscriptionByTopic
|
||||
//
|
||||
// See http://goo.gl/LaVcC for more details.
|
||||
func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err error) {
|
||||
resp = &ListSubscriptionByTopicResponse{}
|
||||
params := makeParams("ListSbubscriptionByTopic")
|
||||
|
||||
if options.NextToken != "" {
|
||||
params["NextToken"] = options.NextToken
|
||||
}
|
||||
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
StatusCode int
|
||||
Code string
|
||||
@@ -394,7 +69,7 @@ type xmlErrors struct {
|
||||
Errors []Error `xml:"Errors>Error"`
|
||||
}
|
||||
|
||||
func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, resp interface{}) error {
|
||||
func (sns *SNS) query(params map[string]string, resp interface{}) error {
|
||||
params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
|
||||
u, err := url.Parse(sns.Region.SNSEndpoint)
|
||||
if err != nil {
|
||||
@@ -409,10 +84,6 @@ func (sns *SNS) query(topic *Topic, message *Message, params map[string]string,
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
//dump, _ := http.DumpResponse(r, true)
|
||||
//println("DUMP:\n", string(dump))
|
||||
//return nil
|
||||
|
||||
if r.StatusCode != 200 {
|
||||
return buildError(r)
|
||||
}
|
||||
|
||||
+12
-3
@@ -40,6 +40,7 @@ func (s *S) TestListTopicsOK(c *C) {
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.Topics[0].SNS, Equals, s.sns)
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "bd10b26c-e30e-11e0-ba29-93c3aca2f103")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
@@ -54,6 +55,7 @@ func (s *S) TestCreateTopic(c *C) {
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.Topic.SNS, Equals, s.sns)
|
||||
c.Assert(resp.Topic.TopicArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "a8dec8b3-33a4-11df-8963-01868b7c937a")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -62,8 +64,8 @@ func (s *S) TestCreateTopic(c *C) {
|
||||
func (s *S) TestDeleteTopic(c *C) {
|
||||
testServer.Response(200, nil, TestDeleteTopicXmlOK)
|
||||
|
||||
t := sns.Topic{nil, "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := s.sns.DeleteTopic(t)
|
||||
t := sns.Topic{s.sns, "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := t.Delete()
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
@@ -115,7 +117,14 @@ func (s *S) TestGetTopicAttributes(c *C) {
|
||||
func (s *S) TestPublish(c *C) {
|
||||
testServer.Response(200, nil, TestPublishXmlOK)
|
||||
|
||||
pubOpt := &sns.PublishOpt{"foobar", "", "subject", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
pubOpt := &sns.PublishOpt{
|
||||
Message: "foobar",
|
||||
MessageStructure: "",
|
||||
Subject: "subject",
|
||||
TopicArn: "arn:aws:sns:us-east-1:123456789012:My-Topic",
|
||||
TargetArn: "arn:aws:sns:us-east-1:123456789012:My-Other-Topic",
|
||||
}
|
||||
|
||||
resp, err := s.sns.Publish(pubOpt)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package sns
|
||||
|
||||
type Subscription struct {
|
||||
Endpoint string
|
||||
Owner string
|
||||
Protocol string
|
||||
SubscriptionArn string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
type ListSubscriptionsResp struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type PublishOpt struct {
|
||||
Message string
|
||||
MessageStructure string
|
||||
Subject string
|
||||
TopicArn string
|
||||
TargetArn string
|
||||
}
|
||||
|
||||
type PublishResp struct {
|
||||
MessageId string `xml:"PublishResult>MessageId"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type SubscribeResponse struct {
|
||||
SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type UnsubscribeResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionResponse struct {
|
||||
SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionOpt struct {
|
||||
AuthenticateOnUnsubscribe string
|
||||
Token string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicResponse struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicOpt struct {
|
||||
NextToken string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
// Publish
|
||||
//
|
||||
// See http://goo.gl/AY2D8 for more details.
|
||||
func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResp, err error) {
|
||||
resp = &PublishResp{}
|
||||
params := makeParams("Publish")
|
||||
|
||||
if options.Subject != "" {
|
||||
params["Subject"] = options.Subject
|
||||
}
|
||||
|
||||
if options.MessageStructure != "" {
|
||||
params["MessageStructure"] = options.MessageStructure
|
||||
}
|
||||
|
||||
if options.Message != "" {
|
||||
params["Message"] = options.Message
|
||||
}
|
||||
|
||||
if options.TopicArn != "" {
|
||||
params["TopicArn"] = options.TopicArn
|
||||
}
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Subscribe
|
||||
//
|
||||
// See http://goo.gl/c3iGS for more details.
|
||||
func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err error) {
|
||||
resp = &SubscribeResponse{}
|
||||
params := makeParams("Subscribe")
|
||||
|
||||
params["Endpoint"] = Endpoint
|
||||
params["Protocol"] = Protocol
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
//
|
||||
// See http://goo.gl/4l5Ge for more details.
|
||||
func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err error) {
|
||||
resp = &UnsubscribeResponse{}
|
||||
params := makeParams("Unsubscribe")
|
||||
|
||||
params["SubscriptionArn"] = SubscriptionArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ConfirmSubscription
|
||||
//
|
||||
// See http://goo.gl/3hXzH for more details.
|
||||
func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err error) {
|
||||
resp = &ConfirmSubscriptionResponse{}
|
||||
params := makeParams("ConfirmSubscription")
|
||||
|
||||
if options.AuthenticateOnUnsubscribe != "" {
|
||||
params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe
|
||||
}
|
||||
|
||||
params["Token"] = options.Token
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ListSubscriptions
|
||||
//
|
||||
// See http://goo.gl/k3aGn for more details.
|
||||
func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResp, err error) {
|
||||
resp = &ListSubscriptionsResp{}
|
||||
params := makeParams("ListSubscriptions")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ListSubscriptionByTopic
|
||||
//
|
||||
// See http://goo.gl/LaVcC for more details.
|
||||
func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err error) {
|
||||
resp = &ListSubscriptionByTopicResponse{}
|
||||
params := makeParams("ListSbubscriptionByTopic")
|
||||
|
||||
if options.NextToken != "" {
|
||||
params["NextToken"] = options.NextToken
|
||||
}
|
||||
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package sns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Topic struct {
|
||||
SNS *SNS
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
type ListTopicsResp struct {
|
||||
Topics []Topic `xml:"ListTopicsResult>Topics>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type CreateTopicResp struct {
|
||||
Topic Topic `xml:"CreateTopicResult"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type DeleteTopicResp struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type GetTopicAttributesResp struct {
|
||||
Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type SetTopicAttributesResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// ListTopics
|
||||
//
|
||||
// See http://goo.gl/lfrMK for more details.
|
||||
func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResp, err error) {
|
||||
resp = &ListTopicsResp{}
|
||||
params := makeParams("ListTopics")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(params, resp)
|
||||
for i, _ := range resp.Topics {
|
||||
resp.Topics[i].SNS = sns
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTopic
|
||||
//
|
||||
// See http://goo.gl/m9aAt for more details.
|
||||
func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResp, err error) {
|
||||
resp = &CreateTopicResp{}
|
||||
params := makeParams("CreateTopic")
|
||||
params["Name"] = Name
|
||||
err = sns.query(params, resp)
|
||||
resp.Topic.SNS = sns
|
||||
return
|
||||
}
|
||||
|
||||
// Delete
|
||||
//
|
||||
// Helper function for deleting a topic
|
||||
func (topic *Topic) Delete() (resp *DeleteTopicResp, err error) {
|
||||
resp = &DeleteTopicResp{}
|
||||
params := makeParams("DeleteTopic")
|
||||
params["TopicArn"] = topic.TopicArn
|
||||
err = topic.SNS.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/WXRoX for more details.
|
||||
func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResp, err error) {
|
||||
resp = &GetTopicAttributesResp{}
|
||||
params := makeParams("GetTopicAttributes")
|
||||
params["TopicArn"] = TopicArn
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/oVYW7 for more details.
|
||||
func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err error) {
|
||||
resp = &SetTopicAttributesResponse{}
|
||||
params := makeParams("SetTopicAttributes")
|
||||
|
||||
if AttributeName == "" || TopicArn == "" {
|
||||
return nil, errors.New("Invalid Attribute Name or TopicArn")
|
||||
}
|
||||
|
||||
params["AttributeName"] = AttributeName
|
||||
params["AttributeValue"] = AttributeValue
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(params, resp)
|
||||
return
|
||||
}
|
||||
@@ -90,23 +90,24 @@ func makeParams(action string) map[string]string {
|
||||
// Rds objects
|
||||
|
||||
type DBInstance struct {
|
||||
Address string `xml:"Endpoint>Address"`
|
||||
AllocatedStorage int `xml:"AllocatedStorage"`
|
||||
AvailabilityZone string `xml:"AvailabilityZone"`
|
||||
BackupRetentionPeriod int `xml:"BackupRetentionPeriod"`
|
||||
DBInstanceClass string `xml:"DBInstanceClass"`
|
||||
DBInstanceIdentifier string `xml:"DBInstanceIdentifier"`
|
||||
DBInstanceStatus string `xml:"DBInstanceStatus"`
|
||||
DBName string `xml:"DBName"`
|
||||
Engine string `xml:"Engine"`
|
||||
EngineVersion string `xml:"EngineVersion"`
|
||||
MasterUsername string `xml:"MasterUsername"`
|
||||
MultiAZ bool `xml:"MultiAZ"`
|
||||
Port int `xml:"Endpoint>Port"`
|
||||
PreferredBackupWindow string `xml:"PreferredBackupWindow"`
|
||||
PreferredMaintenanceWindow string `xml:"PreferredMaintenanceWindow"`
|
||||
VpcSecurityGroupIds []string `xml:"VpcSecurityGroups"`
|
||||
DBSecurityGroupNames []string `xml:"DBSecurityGroups>DBSecurityGroup>DBSecurityGroupName"`
|
||||
Address string `xml:"Endpoint>Address"`
|
||||
AllocatedStorage int `xml:"AllocatedStorage"`
|
||||
AvailabilityZone string `xml:"AvailabilityZone"`
|
||||
BackupRetentionPeriod int `xml:"BackupRetentionPeriod"`
|
||||
DBInstanceClass string `xml:"DBInstanceClass"`
|
||||
DBInstanceIdentifier string `xml:"DBInstanceIdentifier"`
|
||||
DBInstanceStatus string `xml:"DBInstanceStatus"`
|
||||
DBName string `xml:"DBName"`
|
||||
Engine string `xml:"Engine"`
|
||||
EngineVersion string `xml:"EngineVersion"`
|
||||
MasterUsername string `xml:"MasterUsername"`
|
||||
MultiAZ bool `xml:"MultiAZ"`
|
||||
Port int `xml:"Endpoint>Port"`
|
||||
PreferredBackupWindow string `xml:"PreferredBackupWindow"`
|
||||
PreferredMaintenanceWindow string `xml:"PreferredMaintenanceWindow"`
|
||||
VpcSecurityGroupIds []string `xml:"VpcSecurityGroups"`
|
||||
DBSecurityGroupNames []string `xml:"DBSecurityGroups>DBSecurityGroup>DBSecurityGroupName"`
|
||||
DBSubnetGroup DBSubnetGroup `xml:"DBSubnetGroup"`
|
||||
}
|
||||
|
||||
type DBSecurityGroup struct {
|
||||
@@ -119,6 +120,35 @@ type DBSecurityGroup struct {
|
||||
CidrStatuses []string `xml:"IPRanges>IPRange>Status"`
|
||||
}
|
||||
|
||||
type DBSubnetGroup struct {
|
||||
Description string `xml:"DBSubnetGroupDescription"`
|
||||
Name string `xml:"DBSubnetGroupName"`
|
||||
Status string `xml:"SubnetGroupStatus"`
|
||||
SubnetIds []string `xml:"Subnets>Subnet>SubnetIdentifier"`
|
||||
VpcId string `xml:"VpcId"`
|
||||
}
|
||||
|
||||
type DBSnapshot struct {
|
||||
AllocatedStorage int `xml:"AllocatedStorage"`
|
||||
AvailabilityZone string `xml:"AvailabilityZone"`
|
||||
DBInstanceIdentifier string `xml:"DBInstanceIdentifier"`
|
||||
DBSnapshotIdentifier string `xml:"DBSnapshotIdentifier"`
|
||||
Engine string `xml:"Engine"`
|
||||
EngineVersion string `xml:"EngineVersion"`
|
||||
InstanceCreateTime string `xml:"InstanceCreateTime"`
|
||||
Iops int `xml:"Iops"`
|
||||
LicenseModel string `xml:"LicenseModel"`
|
||||
MasterUsername string `xml:"MasterUsername"`
|
||||
OptionGroupName string `xml:"OptionGroupName"`
|
||||
PercentProgress int `xml:"PercentProgress"`
|
||||
Port int `xml:"Port"`
|
||||
SnapshotCreateTime string `xml:"SnapshotCreateTime"`
|
||||
SnapshotType string `xml:"SnapshotType"`
|
||||
SourceRegion string `xml:"SourceRegion"`
|
||||
Status string `xml:"Status"`
|
||||
VpcId string `xml:"VpcId"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Create
|
||||
|
||||
@@ -194,7 +224,7 @@ func (rds *Rds) CreateDBInstance(options *CreateDBInstance) (resp *SimpleResp, e
|
||||
}
|
||||
|
||||
if options.EngineVersion != "" {
|
||||
params["Engine"] = options.EngineVersion
|
||||
params["EngineVersion"] = options.EngineVersion
|
||||
}
|
||||
|
||||
if options.MasterUsername != "" {
|
||||
@@ -263,6 +293,34 @@ func (rds *Rds) CreateDBSecurityGroup(options *CreateDBSecurityGroup) (resp *Sim
|
||||
return
|
||||
}
|
||||
|
||||
// The CreateDBSubnetGroup request parameters
|
||||
type CreateDBSubnetGroup struct {
|
||||
DBSubnetGroupName string
|
||||
DBSubnetGroupDescription string
|
||||
SubnetIds []string
|
||||
}
|
||||
|
||||
func (rds *Rds) CreateDBSubnetGroup(options *CreateDBSubnetGroup) (resp *SimpleResp, err error) {
|
||||
params := makeParams("CreateDBSubnetGroup")
|
||||
|
||||
params["DBSubnetGroupName"] = options.DBSubnetGroupName
|
||||
params["DBSubnetGroupDescription"] = options.DBSubnetGroupDescription
|
||||
|
||||
for j, group := range options.SubnetIds {
|
||||
params["SubnetIds.member."+strconv.Itoa(j+1)] = group
|
||||
}
|
||||
|
||||
resp = &SimpleResp{}
|
||||
|
||||
err = rds.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The CreateDBSecurityGroup request parameters
|
||||
type AuthorizeDBSecurityGroupIngress struct {
|
||||
Cidr string
|
||||
@@ -358,10 +416,75 @@ func (rds *Rds) DescribeDBSecurityGroups(options *DescribeDBSecurityGroups) (res
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeDBSubnetGroups request params
|
||||
type DescribeDBSubnetGroups struct {
|
||||
DBSubnetGroupName string
|
||||
}
|
||||
|
||||
type DescribeDBSubnetGroupsResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
DBSubnetGroups []DBSubnetGroup `xml:"DescribeDBSubnetGroupsResult>DBSubnetGroups>DBSubnetGroup"`
|
||||
}
|
||||
|
||||
func (rds *Rds) DescribeDBSubnetGroups(options *DescribeDBSubnetGroups) (resp *DescribeDBSubnetGroupsResp, err error) {
|
||||
params := makeParams("DescribeDBSubnetGroups")
|
||||
|
||||
params["DBSubnetGroupName"] = options.DBSubnetGroupName
|
||||
|
||||
resp = &DescribeDBSubnetGroupsResp{}
|
||||
|
||||
err = rds.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeDBSnapshots request params
|
||||
type DescribeDBSnapshots struct {
|
||||
DBInstanceIdentifier string
|
||||
DBSnapshotIdentifier string
|
||||
SnapshotType string
|
||||
}
|
||||
|
||||
type DescribeDBSnapshotsResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
DBSnapshots []DBSnapshot `xml:"DescribeDBSnapshotsResult>DBSnapshots>DBSnapshot"`
|
||||
}
|
||||
|
||||
func (rds *Rds) DescribeDBSnapshots(options *DescribeDBSnapshots) (resp *DescribeDBSnapshotsResp, err error) {
|
||||
params := makeParams("DescribeDBSnapshots")
|
||||
|
||||
if options.DBInstanceIdentifier != "" {
|
||||
params["DBInstanceIdentifier"] = options.DBInstanceIdentifier
|
||||
}
|
||||
|
||||
if options.DBSnapshotIdentifier != "" {
|
||||
params["DBSnapshotIdentifier"] = options.DBSnapshotIdentifier
|
||||
}
|
||||
|
||||
if options.SnapshotType != "" {
|
||||
params["SnapshotType"] = options.SnapshotType
|
||||
}
|
||||
|
||||
resp = &DescribeDBSnapshotsResp{}
|
||||
|
||||
err = rds.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteDBInstance request params
|
||||
type DeleteDBInstance struct {
|
||||
DBInstanceIdentifier string
|
||||
SkipFinalSnapshot bool
|
||||
FinalDBSnapshotIdentifier string
|
||||
DBInstanceIdentifier string
|
||||
SkipFinalSnapshot bool
|
||||
}
|
||||
|
||||
func (rds *Rds) DeleteDBInstance(options *DeleteDBInstance) (resp *SimpleResp, err error) {
|
||||
@@ -369,8 +492,12 @@ func (rds *Rds) DeleteDBInstance(options *DeleteDBInstance) (resp *SimpleResp, e
|
||||
|
||||
params["DBInstanceIdentifier"] = options.DBInstanceIdentifier
|
||||
|
||||
// If we don't skip the final snapshot, we need to specify a final
|
||||
// snapshot identifier
|
||||
if options.SkipFinalSnapshot {
|
||||
params["SkipFinalSnapshot"] = "true"
|
||||
} else {
|
||||
params["FinalDBSnapshotIdentifier"] = options.FinalDBSnapshotIdentifier
|
||||
}
|
||||
|
||||
resp = &SimpleResp{}
|
||||
@@ -405,6 +532,112 @@ func (rds *Rds) DeleteDBSecurityGroup(options *DeleteDBSecurityGroup) (resp *Sim
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteDBSubnetGroup request params
|
||||
type DeleteDBSubnetGroup struct {
|
||||
DBSubnetGroupName string
|
||||
}
|
||||
|
||||
func (rds *Rds) DeleteDBSubnetGroup(options *DeleteDBSubnetGroup) (resp *SimpleResp, err error) {
|
||||
params := makeParams("DeleteDBSubnetGroup")
|
||||
|
||||
params["DBSubnetGroupName"] = options.DBSubnetGroupName
|
||||
|
||||
resp = &SimpleResp{}
|
||||
|
||||
err = rds.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type RestoreDBInstanceFromDBSnapshot struct {
|
||||
DBInstanceIdentifier string
|
||||
DBSnapshotIdentifier string
|
||||
AutoMinorVersionUpgrade bool
|
||||
AvailabilityZone string
|
||||
DBInstanceClass string
|
||||
DBName string
|
||||
DBSubnetGroupName string
|
||||
Engine string
|
||||
Iops int
|
||||
LicenseModel string
|
||||
MultiAZ bool
|
||||
OptionGroupName string
|
||||
Port int
|
||||
PubliclyAccessible bool
|
||||
|
||||
SetIops bool
|
||||
SetPort bool
|
||||
}
|
||||
|
||||
func (rds *Rds) RestoreDBInstanceFromDBSnapshot(options *RestoreDBInstanceFromDBSnapshot) (resp *SimpleResp, err error) {
|
||||
params := makeParams("RestoreDBInstanceFromDBSnapshot")
|
||||
|
||||
params["DBInstanceIdentifier"] = options.DBInstanceIdentifier
|
||||
params["DBSnapshotIdentifier"] = options.DBSnapshotIdentifier
|
||||
|
||||
if options.AutoMinorVersionUpgrade {
|
||||
params["AutoMinorVersionUpgrade"] = "true"
|
||||
}
|
||||
|
||||
if options.AvailabilityZone != "" {
|
||||
params["AvailabilityZone"] = options.AvailabilityZone
|
||||
}
|
||||
|
||||
if options.DBInstanceClass != "" {
|
||||
params["DBInstanceClass"] = options.DBInstanceClass
|
||||
}
|
||||
|
||||
if options.DBName != "" {
|
||||
params["DBName"] = options.DBName
|
||||
}
|
||||
|
||||
if options.DBSubnetGroupName != "" {
|
||||
params["DBSubnetGroupName"] = options.DBSubnetGroupName
|
||||
}
|
||||
|
||||
if options.Engine != "" {
|
||||
params["Engine"] = options.Engine
|
||||
}
|
||||
|
||||
if options.SetIops {
|
||||
params["Iops"] = strconv.Itoa(options.Iops)
|
||||
}
|
||||
|
||||
if options.LicenseModel != "" {
|
||||
params["LicenseModel"] = options.LicenseModel
|
||||
}
|
||||
|
||||
if options.MultiAZ {
|
||||
params["MultiAZ"] = "true"
|
||||
}
|
||||
|
||||
if options.OptionGroupName != "" {
|
||||
params["OptionGroupName"] = options.OptionGroupName
|
||||
}
|
||||
|
||||
if options.SetPort {
|
||||
params["Port"] = strconv.Itoa(options.Port)
|
||||
}
|
||||
|
||||
if options.PubliclyAccessible {
|
||||
params["PubliclyAccessible"] = "true"
|
||||
}
|
||||
|
||||
resp = &SimpleResp{}
|
||||
|
||||
err = rds.query(params, resp)
|
||||
|
||||
if err != nil {
|
||||
resp = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Responses
|
||||
|
||||
type SimpleResp struct {
|
||||
|
||||
@@ -80,6 +80,27 @@ func (s *S) Test_CreateDBSecurityGroup(c *C) {
|
||||
c.Assert(resp.RequestId, Equals, "e68ef6fa-afc1-11c3-845a-476777009d19")
|
||||
}
|
||||
|
||||
func (s *S) Test_CreateDBSubnetGroup(c *C) {
|
||||
testServer.Response(200, nil, CreateDBSubnetGroupExample)
|
||||
|
||||
options := rds.CreateDBSubnetGroup{
|
||||
DBSubnetGroupName: "foobarbaz",
|
||||
DBSubnetGroupDescription: "test description",
|
||||
SubnetIds: []string{"subnet-e4d398a1", "subnet-c2bdb6ba"},
|
||||
}
|
||||
|
||||
resp, err := s.rds.CreateDBSubnetGroup(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateDBSubnetGroup"})
|
||||
c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"})
|
||||
c.Assert(req.Form["DBSubnetGroupDescription"], DeepEquals, []string{"test description"})
|
||||
c.Assert(req.Form["SubnetIds.member.1"], DeepEquals, []string{"subnet-e4d398a1"})
|
||||
c.Assert(req.Form["SubnetIds.member.2"], DeepEquals, []string{"subnet-c2bdb6ba"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "3a401b3f-bb9e-11d3-f4c6-37db295f7674")
|
||||
}
|
||||
|
||||
func (s *S) Test_DescribeDBInstances(c *C) {
|
||||
testServer.Response(200, nil, DescribeDBInstancesExample)
|
||||
|
||||
@@ -119,6 +140,25 @@ func (s *S) Test_DescribeDBSecurityGroups(c *C) {
|
||||
c.Assert(resp.DBSecurityGroups[0].CidrStatuses, DeepEquals, []string{"authorized", "authorized", "authorized", "authorized"})
|
||||
}
|
||||
|
||||
func (s *S) Test_DescribeDBSubnetGroups(c *C) {
|
||||
testServer.Response(200, nil, DescribeDBSubnetGroupsExample)
|
||||
|
||||
options := rds.DescribeDBSubnetGroups{
|
||||
DBSubnetGroupName: "foobarbaz",
|
||||
}
|
||||
|
||||
resp, err := s.rds.DescribeDBSubnetGroups(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeDBSubnetGroups"})
|
||||
c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "b783db3b-b98c-11d3-fbc7-5c0aad74da7c")
|
||||
c.Assert(resp.DBSubnetGroups[0].Status, DeepEquals, "Complete")
|
||||
c.Assert(resp.DBSubnetGroups[0].SubnetIds, DeepEquals, []string{"subnet-e8b3e5b1", "subnet-44b2f22e"})
|
||||
c.Assert(resp.DBSubnetGroups[0].VpcId, DeepEquals, "vpc-e7abbdce")
|
||||
}
|
||||
|
||||
func (s *S) Test_DeleteDBInstance(c *C) {
|
||||
testServer.Response(200, nil, DeleteDBInstanceExample)
|
||||
|
||||
@@ -137,6 +177,26 @@ func (s *S) Test_DeleteDBInstance(c *C) {
|
||||
c.Assert(resp.RequestId, Equals, "7369556f-b70d-11c3-faca-6ba18376ea1b")
|
||||
}
|
||||
|
||||
func (s *S) Test_DeleteDBInstance_SnapshotIdentifier(c *C) {
|
||||
testServer.Response(200, nil, DeleteDBInstanceExample)
|
||||
|
||||
options := rds.DeleteDBInstance{
|
||||
DBInstanceIdentifier: "foobarbaz",
|
||||
SkipFinalSnapshot: false,
|
||||
FinalDBSnapshotIdentifier: "bar",
|
||||
}
|
||||
|
||||
resp, err := s.rds.DeleteDBInstance(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteDBInstance"})
|
||||
c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foobarbaz"})
|
||||
c.Assert(req.Form["FinalDBSnapshotIdentifier"], DeepEquals, []string{"bar"})
|
||||
c.Assert(req.Form["SkipFinalSnapshot"], IsNil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7369556f-b70d-11c3-faca-6ba18376ea1b")
|
||||
}
|
||||
|
||||
func (s *S) Test_DeleteDBSecurityGroup(c *C) {
|
||||
testServer.Response(200, nil, DeleteDBSecurityGroupExample)
|
||||
|
||||
@@ -153,6 +213,22 @@ func (s *S) Test_DeleteDBSecurityGroup(c *C) {
|
||||
c.Assert(resp.RequestId, Equals, "7aec7454-ba25-11d3-855b-576787000e19")
|
||||
}
|
||||
|
||||
func (s *S) Test_DeleteDBSubnetGroup(c *C) {
|
||||
testServer.Response(200, nil, DeleteDBSubnetGroupExample)
|
||||
|
||||
options := rds.DeleteDBSubnetGroup{
|
||||
DBSubnetGroupName: "foobarbaz",
|
||||
}
|
||||
|
||||
resp, err := s.rds.DeleteDBSubnetGroup(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteDBSubnetGroup"})
|
||||
c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "6295e5ab-bbf3-11d3-f4c6-37db295f7674")
|
||||
}
|
||||
|
||||
func (s *S) Test_AuthorizeDBSecurityGroupIngress(c *C) {
|
||||
testServer.Response(200, nil, AuthorizeDBSecurityGroupIngressExample)
|
||||
|
||||
@@ -170,3 +246,44 @@ func (s *S) Test_AuthorizeDBSecurityGroupIngress(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "6176b5f8-bfed-11d3-f92b-31fa5e8dbc99")
|
||||
}
|
||||
|
||||
func (s *S) Test_DescribeDBSnapshots(c *C) {
|
||||
testServer.Response(200, nil, DescribeDBSnapshotsExample)
|
||||
|
||||
options := rds.DescribeDBSnapshots{
|
||||
DBInstanceIdentifier: "foobar",
|
||||
DBSnapshotIdentifier: "baz",
|
||||
SnapshotType: "manual",
|
||||
}
|
||||
|
||||
resp, err := s.rds.DescribeDBSnapshots(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeDBSnapshots"})
|
||||
c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foobar"})
|
||||
c.Assert(req.Form["DBSnapshotIdentifier"], DeepEquals, []string{"baz"})
|
||||
c.Assert(req.Form["SnapshotType"], DeepEquals, []string{"manual"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "b7769930-b98c-11d3-f272-7cd6cce12cc5")
|
||||
c.Assert(resp.DBSnapshots[0].OptionGroupName, Equals, "default:mysql-5-6")
|
||||
c.Assert(resp.DBSnapshots[0].Engine, Equals, "mysql")
|
||||
c.Assert(resp.DBSnapshots[0].SnapshotType, Equals, "manual")
|
||||
}
|
||||
|
||||
func (s *S) Test_RestoreDBInstanceFromDBSnapshot(c *C) {
|
||||
testServer.Response(200, nil, RestoreDBInstanceFromDBSnapshotExample)
|
||||
|
||||
options := rds.RestoreDBInstanceFromDBSnapshot{
|
||||
DBInstanceIdentifier: "foo",
|
||||
DBSnapshotIdentifier: "bar",
|
||||
}
|
||||
|
||||
resp, err := s.rds.RestoreDBInstanceFromDBSnapshot(&options)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RestoreDBInstanceFromDBSnapshot"})
|
||||
c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foo"})
|
||||
c.Assert(req.Form["DBSnapshotIdentifier"], DeepEquals, []string{"bar"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "863fd73e-be2b-11d3-855b-576787000e19")
|
||||
}
|
||||
|
||||
+221
@@ -327,3 +327,224 @@ var AuthorizeDBSecurityGroupIngressExample = `
|
||||
</ResponseMetadata>
|
||||
</AuthorizeDBSecurityGroupIngressResponse>
|
||||
`
|
||||
|
||||
var DescribeDBSubnetGroupsExample = `
|
||||
<DescribeDBSubnetGroupsResponse xmlns="http://rds.amazonaws.com/doc/2013-09-09/">
|
||||
<DescribeDBSubnetGroupsResult>
|
||||
<DBSubnetGroups>
|
||||
<DBSubnetGroup>
|
||||
<VpcId>vpc-e7abbdce</VpcId>
|
||||
<SubnetGroupStatus>Complete</SubnetGroupStatus>
|
||||
<DBSubnetGroupDescription>DB subnet group 1</DBSubnetGroupDescription>
|
||||
<DBSubnetGroupName>mydbsubnetgroup1</DBSubnetGroupName>
|
||||
<Subnets>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-e8b3e5b1</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-west-2a</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-44b2f22e</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-west-2b</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
</Subnets>
|
||||
</DBSubnetGroup>
|
||||
<DBSubnetGroup>
|
||||
<VpcId>vpc-c1e17bb8</VpcId>
|
||||
<SubnetGroupStatus>Complete</SubnetGroupStatus>
|
||||
<DBSubnetGroupDescription>My DB Subnet Group 2</DBSubnetGroupDescription>
|
||||
<DBSubnetGroupName>sub-grp-2</DBSubnetGroupName>
|
||||
<Subnets>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-d281ef8a</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-west-2a</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-b381ef9f</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-west-2c</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-e1e17ebd</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-west-2b</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
</Subnets>
|
||||
</DBSubnetGroup>
|
||||
</DBSubnetGroups>
|
||||
</DescribeDBSubnetGroupsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>b783db3b-b98c-11d3-fbc7-5c0aad74da7c</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DescribeDBSubnetGroupsResponse>
|
||||
`
|
||||
|
||||
var DeleteDBSubnetGroupExample = `
|
||||
<DeleteDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2013-09-09/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>6295e5ab-bbf3-11d3-f4c6-37db295f7674</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteDBSubnetGroupResponse>
|
||||
`
|
||||
|
||||
var CreateDBSubnetGroupExample = `
|
||||
<CreateDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2013-09-09/">
|
||||
<CreateDBSubnetGroupResult>
|
||||
<DBSubnetGroup>
|
||||
<VpcId>vpc-33dc97ea</VpcId>
|
||||
<SubnetGroupStatus>Complete</SubnetGroupStatus>
|
||||
<DBSubnetGroupDescription>My new DB Subnet Group</DBSubnetGroupDescription>
|
||||
<DBSubnetGroupName>myawsuser-dbsubnetgroup</DBSubnetGroupName>
|
||||
<Subnets>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-e4d398a1</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-east-1b</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
<Subnet>
|
||||
<SubnetStatus>Active</SubnetStatus>
|
||||
<SubnetIdentifier>subnet-c2bdb6ba</SubnetIdentifier>
|
||||
<SubnetAvailabilityZone>
|
||||
<Name>us-east-1c</Name>
|
||||
<ProvisionedIopsCapable>false</ProvisionedIopsCapable>
|
||||
</SubnetAvailabilityZone>
|
||||
</Subnet>
|
||||
</Subnets>
|
||||
</DBSubnetGroup>
|
||||
</CreateDBSubnetGroupResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>3a401b3f-bb9e-11d3-f4c6-37db295f7674</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateDBSubnetGroupResponse>
|
||||
`
|
||||
|
||||
var DescribeDBSnapshotsExample = `
|
||||
<DescribeDBSnapshotsResponse xmlns="http://rds.amazonaws.com/doc/2013-09-09/">
|
||||
<DescribeDBSnapshotsResult>
|
||||
<DBSnapshots>
|
||||
<DBSnapshot>
|
||||
<Port>3306</Port>
|
||||
<OptionGroupName>default:mysql-5-6</OptionGroupName>
|
||||
<Engine>mysql</Engine>
|
||||
<Status>available</Status>
|
||||
<SnapshotType>manual</SnapshotType>
|
||||
<LicenseModel>general-public-license</LicenseModel>
|
||||
<EngineVersion>5.6.13</EngineVersion>
|
||||
<DBInstanceIdentifier>my-mysqlexampledb</DBInstanceIdentifier>
|
||||
<DBSnapshotIdentifier>my-test-restore-snapshot</DBSnapshotIdentifier>
|
||||
<SnapshotCreateTime>2014-03-28T19:57:16.707Z</SnapshotCreateTime>
|
||||
<AvailabilityZone>us-west-2b</AvailabilityZone>
|
||||
<InstanceCreateTime>2014-01-29T22:58:24.231Z</InstanceCreateTime>
|
||||
<PercentProgress>100</PercentProgress>
|
||||
<AllocatedStorage>5</AllocatedStorage>
|
||||
<MasterUsername>awsmyuser</MasterUsername>
|
||||
</DBSnapshot>
|
||||
<DBSnapshot>
|
||||
<Port>3306</Port>
|
||||
<OptionGroupName>default:mysql-5-6</OptionGroupName>
|
||||
<Engine>mysql</Engine>
|
||||
<Status>available</Status>
|
||||
<SnapshotType>automated</SnapshotType>
|
||||
<LicenseModel>general-public-license</LicenseModel>
|
||||
<EngineVersion>5.6.13</EngineVersion>
|
||||
<DBInstanceIdentifier>my-mysqlexampledb</DBInstanceIdentifier>
|
||||
<DBSnapshotIdentifier>rds:my-mysqlexampledb-2014-04-19-10-08</DBSnapshotIdentifier>
|
||||
<SnapshotCreateTime>2014-04-19T10:09:09.790Z</SnapshotCreateTime>
|
||||
<AvailabilityZone>us-west-2b</AvailabilityZone>
|
||||
<InstanceCreateTime>2014-01-29T22:58:24.231Z</InstanceCreateTime>
|
||||
<PercentProgress>100</PercentProgress>
|
||||
<AllocatedStorage>5</AllocatedStorage>
|
||||
<MasterUsername>awsmyuser</MasterUsername>
|
||||
</DBSnapshot>
|
||||
<DBSnapshot>
|
||||
<Port>3306</Port>
|
||||
<OptionGroupName>default:mysql-5-6</OptionGroupName>
|
||||
<Engine>mysql</Engine>
|
||||
<Status>available</Status>
|
||||
<SnapshotType>automated</SnapshotType>
|
||||
<LicenseModel>general-public-license</LicenseModel>
|
||||
<EngineVersion>5.6.13</EngineVersion>
|
||||
<DBInstanceIdentifier>my-mysqlexampledb</DBInstanceIdentifier>
|
||||
<DBSnapshotIdentifier>rds:my-mysqlexampledb-2014-04-20-10-09</DBSnapshotIdentifier>
|
||||
<SnapshotCreateTime>2014-04-20T10:09:15.446Z</SnapshotCreateTime>
|
||||
<AvailabilityZone>us-west-2b</AvailabilityZone>
|
||||
<InstanceCreateTime>2014-01-29T22:58:24.231Z</InstanceCreateTime>
|
||||
<PercentProgress>100</PercentProgress>
|
||||
<AllocatedStorage>5</AllocatedStorage>
|
||||
<MasterUsername>awsmyuser</MasterUsername>
|
||||
</DBSnapshot>
|
||||
</DBSnapshots>
|
||||
</DescribeDBSnapshotsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>b7769930-b98c-11d3-f272-7cd6cce12cc5</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DescribeDBSnapshotsResponse>
|
||||
`
|
||||
|
||||
var RestoreDBInstanceFromDBSnapshotExample = `
|
||||
<RestoreDBInstanceFromDBSnapshotResponse xmlns="http://rds.amazonaws.com/doc/2013-09-09/">
|
||||
<RestoreDBInstanceFromDBSnapshotResult>
|
||||
<DBInstance>
|
||||
<BackupRetentionPeriod>2</BackupRetentionPeriod>
|
||||
<MultiAZ>false</MultiAZ>
|
||||
<DBInstanceStatus>creating</DBInstanceStatus>
|
||||
<VpcSecurityGroups/>
|
||||
<DBInstanceIdentifier>mysqldb-restored</DBInstanceIdentifier>
|
||||
<PreferredBackupWindow>08:14-08:44</PreferredBackupWindow>
|
||||
<PreferredMaintenanceWindow>fri:04:50-fri:05:20</PreferredMaintenanceWindow>
|
||||
<ReadReplicaDBInstanceIdentifiers/>
|
||||
<Engine>mysql</Engine>
|
||||
<PendingModifiedValues/>
|
||||
<LicenseModel>general-public-license</LicenseModel>
|
||||
<EngineVersion>5.6.13</EngineVersion>
|
||||
<DBParameterGroups>
|
||||
<DBParameterGroup>
|
||||
<ParameterApplyStatus>in-sync</ParameterApplyStatus>
|
||||
<DBParameterGroupName>default.mysql5.6</DBParameterGroupName>
|
||||
</DBParameterGroup>
|
||||
</DBParameterGroups>
|
||||
<OptionGroupMemberships>
|
||||
<OptionGroupMembership>
|
||||
<OptionGroupName>default:mysql-5-6</OptionGroupName>
|
||||
<Status>pending-apply</Status>
|
||||
</OptionGroupMembership>
|
||||
</OptionGroupMemberships>
|
||||
<PubliclyAccessible>true</PubliclyAccessible>
|
||||
<DBSecurityGroups>
|
||||
<DBSecurityGroup>
|
||||
<Status>active</Status>
|
||||
<DBSecurityGroupName>default</DBSecurityGroupName>
|
||||
</DBSecurityGroup>
|
||||
</DBSecurityGroups>
|
||||
<DBName>mysqldb</DBName>
|
||||
<AutoMinorVersionUpgrade>true</AutoMinorVersionUpgrade>
|
||||
<AllocatedStorage>100</AllocatedStorage>
|
||||
<MasterUsername>myawsuser</MasterUsername>
|
||||
<DBInstanceClass>db.m1.medium</DBInstanceClass>
|
||||
</DBInstance>
|
||||
</RestoreDBInstanceFromDBSnapshotResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>863fd73e-be2b-11d3-855b-576787000e19</RequestId>
|
||||
</ResponseMetadata>
|
||||
</RestoreDBInstanceFromDBSnapshotResponse>
|
||||
`
|
||||
|
||||
+26
@@ -94,3 +94,29 @@ var ListResourceRecordSetsExample = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<NextRecordName>testdoc2.example.com</NextRecordName>
|
||||
<NextRecordType>NS</NextRecordType>
|
||||
</ListResourceRecordSetsResponse>`
|
||||
|
||||
var ListHostedZonesExample = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
||||
<HostedZones>
|
||||
<HostedZone>
|
||||
<Id>/hostedzone/Z2K123214213123</Id>
|
||||
<Name>example.com.</Name>
|
||||
<CallerReference>D2224C5B-684A-DB4A-BB9A-E09E3BAFEA7A</CallerReference>
|
||||
<Config>
|
||||
<Comment>Test comment</Comment>
|
||||
</Config>
|
||||
<ResourceRecordSetCount>10</ResourceRecordSetCount>
|
||||
</HostedZone>
|
||||
<HostedZone>
|
||||
<Id>/hostedzone/ZLT12321321124</Id>
|
||||
<Name>sub.example.com.</Name>
|
||||
<CallerReference>A970F076-FCB1-D959-B395-96474CC84EB8</CallerReference>
|
||||
<Config>
|
||||
<Comment>Test comment for subdomain host</Comment>
|
||||
</Config>
|
||||
<ResourceRecordSetCount>4</ResourceRecordSetCount>
|
||||
</HostedZone>
|
||||
</HostedZones>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<MaxItems>100</MaxItems>
|
||||
</ListHostedZonesResponse>`
|
||||
|
||||
+60
-2
@@ -95,6 +95,16 @@ func (r *Route53) query(method, path string, req, resp interface{}) error {
|
||||
if err := enc.EncodeElement(req, start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This is really a sadness, but can't think of a better way to
|
||||
// do this for now in Go's constructs.
|
||||
replace := "<ResourceRecords><ResourceRecord></ResourceRecord></ResourceRecords>"
|
||||
if strings.Contains(bodyBuf.String(), replace) {
|
||||
var newBuf bytes.Buffer
|
||||
newBuf.WriteString(strings.Replace(bodyBuf.String(), replace, "", -1))
|
||||
bodyBuf = &newBuf
|
||||
}
|
||||
|
||||
body = bodyBuf
|
||||
}
|
||||
|
||||
@@ -196,6 +206,33 @@ func (r *Route53) GetHostedZone(ID string) (*GetHostedZoneResponse, error) {
|
||||
return out, err
|
||||
}
|
||||
|
||||
type ListHostedZonesResponse struct {
|
||||
HostedZones []HostedZone `xml:"HostedZones>HostedZone"`
|
||||
Marker string `xml:"Marker"`
|
||||
IsTruncated bool `xml:"IsTruncated"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
MaxItems int `xml:"MaxItems"`
|
||||
}
|
||||
|
||||
func (r *Route53) ListHostedZones(marker string, maxItems int) (*ListHostedZonesResponse, error) {
|
||||
values := url.Values{}
|
||||
|
||||
if marker != "" {
|
||||
values.Add("marker", marker)
|
||||
}
|
||||
|
||||
if maxItems != 0 {
|
||||
values.Add("maxItems", strconv.Itoa(maxItems))
|
||||
}
|
||||
|
||||
out := &ListHostedZonesResponse{}
|
||||
err := r.query("GET", fmt.Sprintf("/%s/hostedzone/", APIVersion), values, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
type GetChangeResponse struct {
|
||||
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
||||
}
|
||||
@@ -232,10 +269,29 @@ type ChangeResourceRecordSetsResponse struct {
|
||||
|
||||
func (r *Route53) ChangeResourceRecordSets(zone string,
|
||||
req *ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) {
|
||||
// This is really sad, but we have to format this differently
|
||||
// for Route53 to make them happy.
|
||||
reqCopy := *req
|
||||
for i, change := range reqCopy.Changes {
|
||||
if len(change.Record.Records) > 1 {
|
||||
var buf bytes.Buffer
|
||||
for _, r := range change.Record.Records {
|
||||
buf.WriteString(fmt.Sprintf(
|
||||
"<ResourceRecord><Value>%s</Value></ResourceRecord>",
|
||||
r))
|
||||
}
|
||||
|
||||
change.Record.Records = nil
|
||||
change.Record.RecordsXML = fmt.Sprintf(
|
||||
"<ResourceRecords>%s</ResourceRecords>", buf.String())
|
||||
reqCopy.Changes[i] = change
|
||||
}
|
||||
}
|
||||
|
||||
zone = CleanZoneID(zone)
|
||||
out := &ChangeResourceRecordSetsResponse{}
|
||||
if err := r.query("POST", fmt.Sprintf("/%s/hostedzone/%s/rrset", APIVersion,
|
||||
zone), req, out); err != nil {
|
||||
zone), reqCopy, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
@@ -261,13 +317,15 @@ type ResourceRecordSet struct {
|
||||
Name string `xml:"Name"`
|
||||
Type string `xml:"Type"`
|
||||
TTL int `xml:"TTL"`
|
||||
Records []string `xml:"ResourceRecords>ResourceRecord>Value"`
|
||||
Records []string `xml:"ResourceRecords>ResourceRecord>Value,omitempty"`
|
||||
SetIdentifier string `xml:"SetIdentifier,omitempty"`
|
||||
Weight int `xml:"Weight,omitempty"`
|
||||
HealthCheckId string `xml:"HealthCheckId,omitempty"`
|
||||
Region string `xml:"Region,omitempty"`
|
||||
Failover string `xml:"Failover,omitempty"`
|
||||
AliasTarget *AliasTarget `xml:"AliasTarget,omitempty"`
|
||||
|
||||
RecordsXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func (r *Route53) ListResourceRecordSets(zone string, lopts *ListOpts) (*ListResourceRecordSetsResponse, error) {
|
||||
|
||||
+19
@@ -157,6 +157,25 @@ func TestListResourceRecordSets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListHostedZones(t *testing.T) {
|
||||
testServer := makeTestServer()
|
||||
client := makeClient(testServer)
|
||||
testServer.Response(200, nil, ListHostedZonesExample)
|
||||
|
||||
resp, err := client.ListHostedZones("", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if resp.HostedZones[0].Name != "example.com." {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
|
||||
if resp.HostedZones[1].Name != "sub.example.com." {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func decode(t *testing.T, r io.Reader, out interface{}) {
|
||||
var buf1 bytes.Buffer
|
||||
var buf2 bytes.Buffer
|
||||
|
||||
@@ -619,7 +619,7 @@ type request struct {
|
||||
// amazonShouldEscape returns true if byte should be escaped
|
||||
func amazonShouldEscape(c byte) bool {
|
||||
return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~' || c == '.' || c == '/')
|
||||
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~' || c == '.' || c == '/' || c == ':')
|
||||
}
|
||||
|
||||
// amazonEscape does uri escaping exactly as Amazon does
|
||||
@@ -706,22 +706,6 @@ func (s3 *S3) prepare(req *request) error {
|
||||
req.path = "/" + req.path
|
||||
}
|
||||
req.signpath = req.path
|
||||
// signpath includes subresource, if present: "?acl", "?delete", "?location", "?logging", or "?torrent"
|
||||
if req.params["acl"] != nil {
|
||||
req.signpath += "?acl"
|
||||
}
|
||||
if req.params["delete"] != nil {
|
||||
req.signpath += "?delete"
|
||||
}
|
||||
if req.params["location"] != nil {
|
||||
req.signpath += "?location"
|
||||
}
|
||||
if req.params["logging"] != nil {
|
||||
req.signpath += "?logging"
|
||||
}
|
||||
if req.params["torrent"] != nil {
|
||||
req.signpath += "?torrent"
|
||||
}
|
||||
|
||||
if req.bucket != "" {
|
||||
req.baseurl = s3.Region.S3BucketEndpoint
|
||||
|
||||
@@ -427,3 +427,9 @@ func (s *S) TestGetKey(c *C) {
|
||||
c.Assert(key.Size, Equals, int64(434234))
|
||||
c.Assert(key.ETag, Equals, GetKeyHeaderDump["ETag"])
|
||||
}
|
||||
|
||||
func (s *S) TestUnescapedColon(c *C) {
|
||||
b := s.s3.Bucket("bucket")
|
||||
u := b.URL("foo:bar")
|
||||
c.Assert(u, Equals, "http://localhost:4444/bucket/foo:bar")
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
@@ -17,6 +18,7 @@ var b64 = base64.StdEncoding
|
||||
|
||||
var s3ParamsToSign = map[string]bool{
|
||||
"acl": true,
|
||||
"delete": true,
|
||||
"location": true,
|
||||
"logging": true,
|
||||
"notification": true,
|
||||
@@ -47,6 +49,11 @@ func sign(auth aws.Auth, method, canonicalPath string, params, headers map[strin
|
||||
headers["x-amz-security-token"] = []string{auth.Token}
|
||||
}
|
||||
|
||||
if auth.SecretKey == "" {
|
||||
// no auth secret; skip signing, e.g. for public read-only buckets.
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
k = strings.ToLower(k)
|
||||
switch k {
|
||||
@@ -111,6 +118,7 @@ func sign(auth aws.Auth, method, canonicalPath string, params, headers map[strin
|
||||
} else {
|
||||
headers["Authorization"] = []string{"AWS " + auth.AccessKey + ":" + string(signature)}
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Printf("Signature payload: %q", payload)
|
||||
log.Printf("Signature: %q", signature)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
// S3 ReST authentication docs: http://goo.gl/G1LrK
|
||||
|
||||
var testAuth = aws.Auth{"0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o", ""}
|
||||
var emptyAuth = aws.Auth{"", "", ""}
|
||||
|
||||
func (s *S) TestSignExampleObjectGet(c *C) {
|
||||
method := "GET"
|
||||
@@ -22,6 +23,17 @@ func (s *S) TestSignExampleObjectGet(c *C) {
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleObjectGetNoAuth(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:36:42 +0000"},
|
||||
}
|
||||
s3.Sign(emptyAuth, method, path, nil, headers)
|
||||
c.Assert(headers["Authorization"], IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleObjectPut(c *C) {
|
||||
method := "PUT"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
@@ -54,6 +66,23 @@ func (s *S) TestSignExampleList(c *C) {
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleListNoAuth(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/"
|
||||
params := map[string][]string{
|
||||
"prefix": {"photos"},
|
||||
"max-keys": {"50"},
|
||||
"marker": {"puppy"},
|
||||
}
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:42:41 +0000"},
|
||||
"User-Agent": {"Mozilla/5.0"},
|
||||
}
|
||||
s3.Sign(emptyAuth, method, path, params, headers)
|
||||
c.Assert(headers["Authorization"], IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleFetch(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/"
|
||||
@@ -69,6 +98,20 @@ func (s *S) TestSignExampleFetch(c *C) {
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleFetchNoAuth(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/"
|
||||
params := map[string][]string{
|
||||
"acl": {""},
|
||||
}
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:44:46 +0000"},
|
||||
}
|
||||
s3.Sign(emptyAuth, method, path, params, headers)
|
||||
c.Assert(headers["Authorization"], IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleDelete(c *C) {
|
||||
method := "DELETE"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
|
||||
@@ -329,6 +329,17 @@ func Lookup(name string) *Flag {
|
||||
return commandLine.formal[name]
|
||||
}
|
||||
|
||||
// IsSet checks whether flag has been actually set
|
||||
func (f *FlagSet) IsSet(name string) bool {
|
||||
_, exists := f.actual[name]
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsSet checks whether flag has been actually set on the command-lien
|
||||
func IsSet(name string) bool {
|
||||
return commandLine.IsSet(name)
|
||||
}
|
||||
|
||||
// Set sets the value of the named flag.
|
||||
func (f *FlagSet) Set(name, value string) error {
|
||||
flag, ok := f.formal[name]
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Andrey Smirnov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,23 @@
|
||||
go-ftp-protocol
|
||||
===============
|
||||
|
||||
Plugin for http.Transport with support for ftp:// protocol in Go.
|
||||
|
||||
Limitations: only anonymous FTP servers, only file retrieval operations.
|
||||
|
||||
Internally connections to FTP servers are cached and re-used when possible.
|
||||
|
||||
Example usage:
|
||||
|
||||
import "github.com/smira/go-ftp-protocol/protocol"
|
||||
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
|
||||
License: MIT
|
||||
|
||||
Base on FTP client library: http://github.com/jlaffaye/ftp/
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// Package protocol implements ftp:// scheme plugin for http.Transport
|
||||
//
|
||||
// github.com/jlaffaye/ftp library is used internally as FTP client implementation.
|
||||
//
|
||||
// Limitations: only anonymous FTP servers, only file retrieval operations.
|
||||
//
|
||||
// Internally connections to FTP servers are cached and re-used when possible.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// transport := &http.Transport{}
|
||||
// transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
// client := &http.Client{Transport: transport}
|
||||
// resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jlaffaye/ftp"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FTPRoundTripper is an implementation of net/http.RoundTripper on top of FTP client
|
||||
type FTPRoundTripper struct {
|
||||
lock sync.Mutex
|
||||
idleConnections map[string][]*ftp.ServerConn
|
||||
}
|
||||
|
||||
// verify interface
|
||||
var (
|
||||
_ http.RoundTripper = &FTPRoundTripper{}
|
||||
)
|
||||
|
||||
type readCloserWrapper struct {
|
||||
body io.ReadCloser
|
||||
rt *FTPRoundTripper
|
||||
hostport string
|
||||
conn *ftp.ServerConn
|
||||
}
|
||||
|
||||
func (w *readCloserWrapper) Read(p []byte) (n int, err error) {
|
||||
return w.body.Read(p)
|
||||
}
|
||||
|
||||
func (w *readCloserWrapper) Close() error {
|
||||
err := w.body.Close()
|
||||
if err == nil {
|
||||
w.rt.putConnection(w.hostport, w.conn)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rt *FTPRoundTripper) getConnection(hostport string) (conn *ftp.ServerConn, err error) {
|
||||
rt.lock.Lock()
|
||||
conns, ok := rt.idleConnections[hostport]
|
||||
if ok && len(conns) > 0 {
|
||||
conn = conns[0]
|
||||
rt.idleConnections[hostport] = conns[1:]
|
||||
rt.lock.Unlock()
|
||||
return
|
||||
}
|
||||
rt.lock.Unlock()
|
||||
|
||||
conn, err = ftp.Connect(hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Login("anonymous", "anonymous")
|
||||
if err != nil {
|
||||
conn.Quit()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (rt *FTPRoundTripper) putConnection(hostport string, conn *ftp.ServerConn) {
|
||||
rt.lock.Lock()
|
||||
defer rt.lock.Unlock()
|
||||
|
||||
if rt.idleConnections == nil {
|
||||
rt.idleConnections = make(map[string][]*ftp.ServerConn)
|
||||
}
|
||||
|
||||
rt.idleConnections[hostport] = append(rt.idleConnections[hostport], conn)
|
||||
}
|
||||
|
||||
// RoundTrip parses incoming GET "HTTP" request and transforms it into
|
||||
// commands to ftp client
|
||||
func (rt *FTPRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
if request.URL.Scheme != "ftp" {
|
||||
return nil, fmt.Errorf("only ftp protocol is supported, got %s", request.Proto)
|
||||
}
|
||||
|
||||
if request.Method != "GET" {
|
||||
return nil, fmt.Errorf("only GET method is supported, got %s", request.Method)
|
||||
}
|
||||
|
||||
hostport := request.URL.Host
|
||||
if strings.Index(hostport, ":") == -1 {
|
||||
hostport = hostport + ":21"
|
||||
}
|
||||
|
||||
connection, err := rt.getConnection(hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body io.ReadCloser
|
||||
body, err = connection.Retr(request.URL.Path)
|
||||
|
||||
if err != nil {
|
||||
if te, ok := err.(*textproto.Error); ok {
|
||||
rt.putConnection(hostport, connection)
|
||||
|
||||
if te.Code == ftp.StatusFileUnavailable {
|
||||
return &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Proto: "FTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "FTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Body: &readCloserWrapper{
|
||||
body: body,
|
||||
rt: rt,
|
||||
hostport: hostport,
|
||||
conn: connection,
|
||||
},
|
||||
ContentLength: -1,
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProtocol(t *testing.T) {
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") {
|
||||
t.Fatalf("unexpected content: %s", content)
|
||||
}
|
||||
|
||||
resp, err = client.Get("ftp://ftp.ru.debian.org/debian/missing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 404 {
|
||||
t.Fatalf("resp.StatusCode 404 != %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrent(t *testing.T) {
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
const concurrency = 4
|
||||
const count = 10
|
||||
|
||||
done := make(chan struct{}, concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
for j := 0; j < 10; j++ {
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") {
|
||||
t.Fatalf("unexpected content: %s", content)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
|
||||
script: go test ./...
|
||||
@@ -1,5 +1,7 @@
|
||||
This is an implementation of the [LevelDB key/value database](http:code.google.com/p/leveldb) in the [Go programming language](http:golang.org).
|
||||
|
||||
[](https://travis-ci.org/syndtr/goleveldb)
|
||||
|
||||
Installation
|
||||
-----------
|
||||
|
||||
@@ -8,7 +10,7 @@ Installation
|
||||
Requirements
|
||||
-----------
|
||||
|
||||
* Need at least `go1.1` or newer.
|
||||
* Need at least `go1.2` or newer.
|
||||
|
||||
Usage
|
||||
-----------
|
||||
@@ -66,6 +68,17 @@ Iterate over subset of database content:
|
||||
err = iter.Error()
|
||||
...
|
||||
|
||||
Iterate over subset of database content with a particular prefix:
|
||||
|
||||
iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil)
|
||||
for iter.Next() {
|
||||
// Use key/value.
|
||||
...
|
||||
}
|
||||
iter.Release()
|
||||
err = iter.Error()
|
||||
...
|
||||
|
||||
Batch writes:
|
||||
|
||||
batch := new(leveldb.Batch)
|
||||
|
||||
+9
-6
@@ -170,7 +170,7 @@ func (p *dbBench) writes(perBatch int) {
|
||||
b.SetBytes(116)
|
||||
}
|
||||
|
||||
func (p *dbBench) drop() {
|
||||
func (p *dbBench) gc() {
|
||||
p.keys, p.values = nil, nil
|
||||
runtime.GC()
|
||||
}
|
||||
@@ -249,6 +249,9 @@ func (p *dbBench) newIter() iterator.Iterator {
|
||||
}
|
||||
|
||||
func (p *dbBench) close() {
|
||||
if bp, err := p.db.GetProperty("leveldb.blockpool"); err == nil {
|
||||
p.b.Log("Block pool stats: ", bp)
|
||||
}
|
||||
p.db.Close()
|
||||
p.stor.Close()
|
||||
os.RemoveAll(benchDB)
|
||||
@@ -331,7 +334,7 @@ func BenchmarkDBRead(b *testing.B) {
|
||||
p := openDBBench(b, false)
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.drop()
|
||||
p.gc()
|
||||
|
||||
iter := p.newIter()
|
||||
b.ResetTimer()
|
||||
@@ -362,7 +365,7 @@ func BenchmarkDBReadUncompressed(b *testing.B) {
|
||||
p := openDBBench(b, true)
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.drop()
|
||||
p.gc()
|
||||
|
||||
iter := p.newIter()
|
||||
b.ResetTimer()
|
||||
@@ -379,7 +382,7 @@ func BenchmarkDBReadTable(b *testing.B) {
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.reopen()
|
||||
p.drop()
|
||||
p.gc()
|
||||
|
||||
iter := p.newIter()
|
||||
b.ResetTimer()
|
||||
@@ -395,7 +398,7 @@ func BenchmarkDBReadReverse(b *testing.B) {
|
||||
p := openDBBench(b, false)
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.drop()
|
||||
p.gc()
|
||||
|
||||
iter := p.newIter()
|
||||
b.ResetTimer()
|
||||
@@ -413,7 +416,7 @@ func BenchmarkDBReadReverseTable(b *testing.B) {
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.reopen()
|
||||
p.drop()
|
||||
p.gc()
|
||||
|
||||
iter := p.newIter()
|
||||
b.ResetTimer()
|
||||
|
||||
+93
-59
@@ -11,115 +11,149 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// SetFunc used by Namespace.Get method to create a cache object. SetFunc
|
||||
// may return ok false, in that case the cache object will not be created.
|
||||
type SetFunc func() (ok bool, value interface{}, charge int, fin SetFin)
|
||||
// SetFunc is the function that will be called by Namespace.Get to create
|
||||
// a cache object, if charge is less than one than the cache object will
|
||||
// not be registered to cache tree, if value is nil then the cache object
|
||||
// will not be created.
|
||||
type SetFunc func() (charge int, value interface{})
|
||||
|
||||
// SetFin will be called when corresponding cache object are released.
|
||||
type SetFin func()
|
||||
// DelFin is the function that will be called as the result of a delete operation.
|
||||
// Exist == true is indication that the object is exist, and pending == true is
|
||||
// indication of deletion already happen but haven't done yet (wait for all handles
|
||||
// to be released). And exist == false means the object doesn't exist.
|
||||
type DelFin func(exist, pending bool)
|
||||
|
||||
// DelFin will be called when corresponding cache object are released.
|
||||
// DelFin will be called after SetFin. The exist is true if the corresponding
|
||||
// cache object is actually exist in the cache tree.
|
||||
type DelFin func(exist bool)
|
||||
// PurgeFin is the function that will be called as the result of a purge operation.
|
||||
type PurgeFin func(ns, key uint64)
|
||||
|
||||
// PurgeFin will be called when corresponding cache object are released.
|
||||
// PurgeFin will be called after SetFin. If PurgeFin present DelFin will
|
||||
// not be executed but passed to the PurgeFin, it is up to the caller
|
||||
// to call it or not.
|
||||
type PurgeFin func(ns, key uint64, delfin DelFin)
|
||||
|
||||
// Cache is a cache tree.
|
||||
// Cache is a cache tree. A cache instance must be goroutine-safe.
|
||||
type Cache interface {
|
||||
// SetCapacity sets cache capacity.
|
||||
// SetCapacity sets cache tree capacity.
|
||||
SetCapacity(capacity int)
|
||||
|
||||
// GetNamespace gets or creates a cache namespace for the given id.
|
||||
// Capacity returns cache tree capacity.
|
||||
Capacity() int
|
||||
|
||||
// Used returns used cache tree capacity.
|
||||
Used() int
|
||||
|
||||
// Size returns entire alive cache objects size.
|
||||
Size() int
|
||||
|
||||
// NumObjects returns number of alive objects.
|
||||
NumObjects() int
|
||||
|
||||
// GetNamespace gets cache namespace with the given id.
|
||||
// GetNamespace is never return nil.
|
||||
GetNamespace(id uint64) Namespace
|
||||
|
||||
// Purge purges all cache namespaces, read Namespace.Purge method documentation.
|
||||
// PurgeNamespace purges cache namespace with the given id from this cache tree.
|
||||
// Also read Namespace.Purge.
|
||||
PurgeNamespace(id uint64, fin PurgeFin)
|
||||
|
||||
// ZapNamespace detaches cache namespace with the given id from this cache tree.
|
||||
// Also read Namespace.Zap.
|
||||
ZapNamespace(id uint64)
|
||||
|
||||
// Purge purges all cache namespace from this cache tree.
|
||||
// This is behave the same as calling Namespace.Purge method on all cache namespace.
|
||||
Purge(fin PurgeFin)
|
||||
|
||||
// Zap zaps all cache namespaces, read Namespace.Zap method documentation.
|
||||
Zap(closed bool)
|
||||
// Zap detaches all cache namespace from this cache tree.
|
||||
// This is behave the same as calling Namespace.Zap method on all cache namespace.
|
||||
Zap()
|
||||
}
|
||||
|
||||
// Namespace is a cache namespace.
|
||||
// Namespace is a cache namespace. A namespace instance must be goroutine-safe.
|
||||
type Namespace interface {
|
||||
// Get gets cache object for the given key. The given SetFunc (if not nil) will
|
||||
// be called if the given key does not exist.
|
||||
// If the given key does not exist, SetFunc is nil or SetFunc return ok false, Get
|
||||
// will return ok false.
|
||||
Get(key uint64, setf SetFunc) (obj Object, ok bool)
|
||||
|
||||
// Get deletes cache object for the given key. If exist the cache object will
|
||||
// be deleted later when all of its handles have been released (i.e. no one use
|
||||
// it anymore) and the given DelFin (if not nil) will finally be executed. If
|
||||
// such cache object does not exist the given DelFin will be executed anyway.
|
||||
// Get gets cache object with the given key.
|
||||
// If cache object is not found and setf is not nil, Get will atomically creates
|
||||
// the cache object by calling setf. Otherwise Get will returns nil.
|
||||
//
|
||||
// Delete returns true if such cache object exist.
|
||||
// The returned cache handle should be released after use by calling Release
|
||||
// method.
|
||||
Get(key uint64, setf SetFunc) Handle
|
||||
|
||||
// Delete removes cache object with the given key from cache tree.
|
||||
// A deleted cache object will be released as soon as all of its handles have
|
||||
// been released.
|
||||
// Delete only happen once, subsequent delete will consider cache object doesn't
|
||||
// exist, even if the cache object ins't released yet.
|
||||
//
|
||||
// If not nil, fin will be called if the cache object doesn't exist or when
|
||||
// finally be released.
|
||||
//
|
||||
// Delete returns true if such cache object exist and never been deleted.
|
||||
Delete(key uint64, fin DelFin) bool
|
||||
|
||||
// Purge deletes all cache objects, read Delete method documentation.
|
||||
// Purge removes all cache objects within this namespace from cache tree.
|
||||
// This is the same as doing delete on all cache objects.
|
||||
//
|
||||
// If not nil, fin will be called on all cache objects when its finally be
|
||||
// released.
|
||||
Purge(fin PurgeFin)
|
||||
|
||||
// Zap detaches the namespace from the cache tree and delete all its cache
|
||||
// objects. The cache objects deletion and finalizers execution are happen
|
||||
// immediately, even if its existing handles haven't yet been released.
|
||||
// A zapped namespace can't never be filled again.
|
||||
// If closed is false then the Get function will always call the given SetFunc
|
||||
// if it is not nil, but resultant of the SetFunc will not be cached.
|
||||
Zap(closed bool)
|
||||
// Zap detaches namespace from cache tree and release all its cache objects.
|
||||
// A zapped namespace can never be filled again.
|
||||
// Calling Get on zapped namespace will always return nil.
|
||||
Zap()
|
||||
}
|
||||
|
||||
// Object is a cache object.
|
||||
type Object interface {
|
||||
// Release releases the cache object. Other methods should not be called
|
||||
// after the cache object has been released.
|
||||
// Handle is a cache handle.
|
||||
type Handle interface {
|
||||
// Release releases this cache handle. This method can be safely called mutiple
|
||||
// times.
|
||||
Release()
|
||||
|
||||
// Value returns value of the cache object.
|
||||
// Value returns value of this cache handle.
|
||||
// Value will returns nil after this cache handle have be released.
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
const (
|
||||
DelNotExist = iota
|
||||
DelExist
|
||||
DelPendig
|
||||
)
|
||||
|
||||
// Namespace state.
|
||||
type nsState int
|
||||
|
||||
const (
|
||||
nsEffective nsState = iota
|
||||
nsZapped
|
||||
nsClosed
|
||||
)
|
||||
|
||||
// Node state.
|
||||
type nodeState int
|
||||
|
||||
const (
|
||||
nodeEffective nodeState = iota
|
||||
nodeZero nodeState = iota
|
||||
nodeEffective
|
||||
nodeEvicted
|
||||
nodeRemoved
|
||||
nodeDeleted
|
||||
)
|
||||
|
||||
// Fake object.
|
||||
type fakeObject struct {
|
||||
// Fake handle.
|
||||
type fakeHandle struct {
|
||||
value interface{}
|
||||
fin func()
|
||||
once uint32
|
||||
}
|
||||
|
||||
func (o *fakeObject) Value() interface{} {
|
||||
if atomic.LoadUint32(&o.once) == 0 {
|
||||
return o.value
|
||||
func (h *fakeHandle) Value() interface{} {
|
||||
if atomic.LoadUint32(&h.once) == 0 {
|
||||
return h.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *fakeObject) Release() {
|
||||
if !atomic.CompareAndSwapUint32(&o.once, 0, 1) {
|
||||
func (h *fakeHandle) Release() {
|
||||
if !atomic.CompareAndSwapUint32(&h.once, 0, 1) {
|
||||
return
|
||||
}
|
||||
if o.fin != nil {
|
||||
o.fin()
|
||||
o.fin = nil
|
||||
if h.fin != nil {
|
||||
h.fin()
|
||||
h.fin = nil
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+486
-67
@@ -8,14 +8,32 @@ package cache
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func set(ns Namespace, key uint64, value interface{}, charge int, fin func()) Object {
|
||||
obj, _ := ns.Get(key, func() (bool, interface{}, int, SetFin) {
|
||||
return true, value, charge, fin
|
||||
type releaserFunc struct {
|
||||
fn func()
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (r releaserFunc) Release() {
|
||||
if r.fn != nil {
|
||||
r.fn()
|
||||
}
|
||||
}
|
||||
|
||||
func set(ns Namespace, key uint64, value interface{}, charge int, relf func()) Handle {
|
||||
return ns.Get(key, func() (int, interface{}) {
|
||||
if relf != nil {
|
||||
return charge, releaserFunc{relf, value}
|
||||
} else {
|
||||
return charge, value
|
||||
}
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
func TestCache_HitMiss(t *testing.T) {
|
||||
@@ -43,29 +61,31 @@ func TestCache_HitMiss(t *testing.T) {
|
||||
setfin++
|
||||
}).Release()
|
||||
for j, y := range cases {
|
||||
r, ok := ns.Get(y.key, nil)
|
||||
h := ns.Get(y.key, nil)
|
||||
if j <= i {
|
||||
// should hit
|
||||
if !ok {
|
||||
if h == nil {
|
||||
t.Errorf("case '%d' iteration '%d' is miss", i, j)
|
||||
} else if r.Value().(string) != y.value {
|
||||
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, r.Value().(string), y.value)
|
||||
} else {
|
||||
if x := h.Value().(releaserFunc).value.(string); x != y.value {
|
||||
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// should miss
|
||||
if ok {
|
||||
t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, r.Value().(string))
|
||||
if h != nil {
|
||||
t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, h.Value().(releaserFunc).value.(string))
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
r.Release()
|
||||
if h != nil {
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, x := range cases {
|
||||
finalizerOk := false
|
||||
ns.Delete(x.key, func(exist bool) {
|
||||
ns.Delete(x.key, func(exist, pending bool) {
|
||||
finalizerOk = true
|
||||
})
|
||||
|
||||
@@ -74,22 +94,24 @@ func TestCache_HitMiss(t *testing.T) {
|
||||
}
|
||||
|
||||
for j, y := range cases {
|
||||
r, ok := ns.Get(y.key, nil)
|
||||
h := ns.Get(y.key, nil)
|
||||
if j > i {
|
||||
// should hit
|
||||
if !ok {
|
||||
if h == nil {
|
||||
t.Errorf("case '%d' iteration '%d' is miss", i, j)
|
||||
} else if r.Value().(string) != y.value {
|
||||
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, r.Value().(string), y.value)
|
||||
} else {
|
||||
if x := h.Value().(releaserFunc).value.(string); x != y.value {
|
||||
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// should miss
|
||||
if ok {
|
||||
t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, r.Value().(string))
|
||||
if h != nil {
|
||||
t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, h.Value().(releaserFunc).value.(string))
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
r.Release()
|
||||
if h != nil {
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,42 +129,42 @@ func TestLRUCache_Eviction(t *testing.T) {
|
||||
set(ns, 3, 3, 1, nil).Release()
|
||||
set(ns, 4, 4, 1, nil).Release()
|
||||
set(ns, 5, 5, 1, nil).Release()
|
||||
if r, ok := ns.Get(2, nil); ok { // 1,3,4,5,2
|
||||
r.Release()
|
||||
if h := ns.Get(2, nil); h != nil { // 1,3,4,5,2
|
||||
h.Release()
|
||||
}
|
||||
set(ns, 9, 9, 10, nil).Release() // 5,2,9
|
||||
|
||||
for _, x := range []uint64{9, 2, 5, 1} {
|
||||
r, ok := ns.Get(x, nil)
|
||||
if !ok {
|
||||
t.Errorf("miss for key '%d'", x)
|
||||
for _, key := range []uint64{9, 2, 5, 1} {
|
||||
h := ns.Get(key, nil)
|
||||
if h == nil {
|
||||
t.Errorf("miss for key '%d'", key)
|
||||
} else {
|
||||
if r.Value().(int) != int(x) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int))
|
||||
if x := h.Value().(int); x != int(key) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
||||
}
|
||||
r.Release()
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
o1.Release()
|
||||
for _, x := range []uint64{1, 2, 5} {
|
||||
r, ok := ns.Get(x, nil)
|
||||
if !ok {
|
||||
t.Errorf("miss for key '%d'", x)
|
||||
for _, key := range []uint64{1, 2, 5} {
|
||||
h := ns.Get(key, nil)
|
||||
if h == nil {
|
||||
t.Errorf("miss for key '%d'", key)
|
||||
} else {
|
||||
if r.Value().(int) != int(x) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int))
|
||||
if x := h.Value().(int); x != int(key) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
||||
}
|
||||
r.Release()
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
for _, x := range []uint64{3, 4, 9} {
|
||||
r, ok := ns.Get(x, nil)
|
||||
if ok {
|
||||
t.Errorf("hit for key '%d'", x)
|
||||
if r.Value().(int) != int(x) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int))
|
||||
for _, key := range []uint64{3, 4, 9} {
|
||||
h := ns.Get(key, nil)
|
||||
if h != nil {
|
||||
t.Errorf("hit for key '%d'", key)
|
||||
if x := h.Value().(int); x != int(key) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
||||
}
|
||||
r.Release()
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,16 +175,15 @@ func TestLRUCache_SetGet(t *testing.T) {
|
||||
for i := 0; i < 200; i++ {
|
||||
n := uint64(rand.Intn(99999) % 20)
|
||||
set(ns, n, n, 1, nil).Release()
|
||||
if p, ok := ns.Get(n, nil); ok {
|
||||
if p.Value() == nil {
|
||||
if h := ns.Get(n, nil); h != nil {
|
||||
if h.Value() == nil {
|
||||
t.Errorf("key '%d' contains nil value", n)
|
||||
} else {
|
||||
got := p.Value().(uint64)
|
||||
if got != n {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", n, n, got)
|
||||
if x := h.Value().(uint64); x != n {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", n, n, x)
|
||||
}
|
||||
}
|
||||
p.Release()
|
||||
h.Release()
|
||||
} else {
|
||||
t.Errorf("key '%d' doesn't exist", n)
|
||||
}
|
||||
@@ -176,31 +197,429 @@ func TestLRUCache_Purge(t *testing.T) {
|
||||
o2 := set(ns1, 2, 2, 1, nil)
|
||||
ns1.Purge(nil)
|
||||
set(ns1, 3, 3, 1, nil).Release()
|
||||
for _, x := range []uint64{1, 2, 3} {
|
||||
r, ok := ns1.Get(x, nil)
|
||||
if !ok {
|
||||
t.Errorf("miss for key '%d'", x)
|
||||
for _, key := range []uint64{1, 2, 3} {
|
||||
h := ns1.Get(key, nil)
|
||||
if h == nil {
|
||||
t.Errorf("miss for key '%d'", key)
|
||||
} else {
|
||||
if r.Value().(int) != int(x) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int))
|
||||
if x := h.Value().(int); x != int(key) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
||||
}
|
||||
r.Release()
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
o1.Release()
|
||||
o2.Release()
|
||||
for _, x := range []uint64{1, 2} {
|
||||
r, ok := ns1.Get(x, nil)
|
||||
if ok {
|
||||
t.Errorf("hit for key '%d'", x)
|
||||
if r.Value().(int) != int(x) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int))
|
||||
for _, key := range []uint64{1, 2} {
|
||||
h := ns1.Get(key, nil)
|
||||
if h != nil {
|
||||
t.Errorf("hit for key '%d'", key)
|
||||
if x := h.Value().(int); x != int(key) {
|
||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
||||
}
|
||||
r.Release()
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testingCacheObjectCounter struct {
|
||||
created uint
|
||||
released uint
|
||||
}
|
||||
|
||||
func (c *testingCacheObjectCounter) createOne() {
|
||||
c.created++
|
||||
}
|
||||
|
||||
func (c *testingCacheObjectCounter) releaseOne() {
|
||||
c.released++
|
||||
}
|
||||
|
||||
type testingCacheObject struct {
|
||||
t *testing.T
|
||||
cnt *testingCacheObjectCounter
|
||||
|
||||
ns, key uint64
|
||||
|
||||
releaseCalled bool
|
||||
}
|
||||
|
||||
func (x *testingCacheObject) Release() {
|
||||
if !x.releaseCalled {
|
||||
x.releaseCalled = true
|
||||
x.cnt.releaseOne()
|
||||
} else {
|
||||
x.t.Errorf("duplicate setfin NS#%d KEY#%s", x.ns, x.key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLRUCache_ConcurrentSetGet(t *testing.T) {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
t.Logf("seed=%d", seed)
|
||||
|
||||
const (
|
||||
N = 2000000
|
||||
M = 4000
|
||||
C = 3
|
||||
)
|
||||
|
||||
var set, get uint32
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
c := NewLRUCache(M / 4)
|
||||
for ni := uint64(0); ni < C; ni++ {
|
||||
r0 := rand.New(rand.NewSource(seed + int64(ni)))
|
||||
r1 := rand.New(rand.NewSource(seed + int64(ni) + 1))
|
||||
ns := c.GetNamespace(ni)
|
||||
|
||||
wg.Add(2)
|
||||
go func(ns Namespace, r *rand.Rand) {
|
||||
for i := 0; i < N; i++ {
|
||||
x := uint64(r.Int63n(M))
|
||||
o := ns.Get(x, func() (int, interface{}) {
|
||||
atomic.AddUint32(&set, 1)
|
||||
return 1, x
|
||||
})
|
||||
if v := o.Value().(uint64); v != x {
|
||||
t.Errorf("#%d invalid value, got=%d", x, v)
|
||||
}
|
||||
o.Release()
|
||||
}
|
||||
wg.Done()
|
||||
}(ns, r0)
|
||||
go func(ns Namespace, r *rand.Rand) {
|
||||
for i := 0; i < N; i++ {
|
||||
x := uint64(r.Int63n(M))
|
||||
o := ns.Get(x, nil)
|
||||
if o != nil {
|
||||
atomic.AddUint32(&get, 1)
|
||||
if v := o.Value().(uint64); v != x {
|
||||
t.Errorf("#%d invalid value, got=%d", x, v)
|
||||
}
|
||||
o.Release()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(ns, r1)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
t.Logf("set=%d get=%d", set, get)
|
||||
}
|
||||
|
||||
func TestLRUCache_Finalizer(t *testing.T) {
|
||||
const (
|
||||
capacity = 100
|
||||
goroutines = 100
|
||||
iterations = 10000
|
||||
keymax = 8000
|
||||
)
|
||||
|
||||
cnt := &testingCacheObjectCounter{}
|
||||
|
||||
c := NewLRUCache(capacity)
|
||||
|
||||
type instance struct {
|
||||
seed int64
|
||||
rnd *rand.Rand
|
||||
nsid uint64
|
||||
ns Namespace
|
||||
effective int
|
||||
handles []Handle
|
||||
handlesMap map[uint64]int
|
||||
|
||||
delete bool
|
||||
purge bool
|
||||
zap bool
|
||||
wantDel int
|
||||
delfinCalled int
|
||||
delfinCalledAll int
|
||||
delfinCalledEff int
|
||||
purgefinCalled int
|
||||
}
|
||||
|
||||
instanceGet := func(p *instance, key uint64) {
|
||||
h := p.ns.Get(key, func() (charge int, value interface{}) {
|
||||
to := &testingCacheObject{
|
||||
t: t, cnt: cnt,
|
||||
ns: p.nsid,
|
||||
key: key,
|
||||
}
|
||||
p.effective++
|
||||
cnt.createOne()
|
||||
return 1, releaserFunc{func() {
|
||||
to.Release()
|
||||
p.effective--
|
||||
}, to}
|
||||
})
|
||||
p.handles = append(p.handles, h)
|
||||
p.handlesMap[key] = p.handlesMap[key] + 1
|
||||
}
|
||||
instanceRelease := func(p *instance, i int) {
|
||||
h := p.handles[i]
|
||||
key := h.Value().(releaserFunc).value.(*testingCacheObject).key
|
||||
if n := p.handlesMap[key]; n == 0 {
|
||||
t.Fatal("key ref == 0")
|
||||
} else if n > 1 {
|
||||
p.handlesMap[key] = n - 1
|
||||
} else {
|
||||
delete(p.handlesMap, key)
|
||||
}
|
||||
h.Release()
|
||||
p.handles = append(p.handles[:i], p.handles[i+1:]...)
|
||||
p.handles[len(p.handles) : len(p.handles)+1][0] = nil
|
||||
}
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
t.Logf("seed=%d", seed)
|
||||
|
||||
instances := make([]*instance, goroutines)
|
||||
for i := range instances {
|
||||
p := &instance{}
|
||||
p.handlesMap = make(map[uint64]int)
|
||||
p.seed = seed + int64(i)
|
||||
p.rnd = rand.New(rand.NewSource(p.seed))
|
||||
p.nsid = uint64(i)
|
||||
p.ns = c.GetNamespace(p.nsid)
|
||||
p.delete = i%6 == 0
|
||||
p.purge = i%8 == 0
|
||||
p.zap = i%12 == 0 || i%3 == 0
|
||||
instances[i] = p
|
||||
}
|
||||
|
||||
runr := rand.New(rand.NewSource(seed - 1))
|
||||
run := func(rnd *rand.Rand, x []*instance, init func(p *instance) bool, fn func(p *instance, i int) bool) {
|
||||
var (
|
||||
rx []*instance
|
||||
rn []int
|
||||
)
|
||||
if init == nil {
|
||||
rx = append([]*instance{}, x...)
|
||||
rn = make([]int, len(x))
|
||||
} else {
|
||||
for _, p := range x {
|
||||
if init(p) {
|
||||
rx = append(rx, p)
|
||||
rn = append(rn, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
for len(rx) > 0 {
|
||||
i := rand.Intn(len(rx))
|
||||
if fn(rx[i], rn[i]) {
|
||||
rn[i]++
|
||||
} else {
|
||||
rx = append(rx[:i], rx[i+1:]...)
|
||||
rn = append(rn[:i], rn[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get and release.
|
||||
run(runr, instances, nil, func(p *instance, i int) bool {
|
||||
if i < iterations {
|
||||
if len(p.handles) == 0 || p.rnd.Int()%2 == 0 {
|
||||
instanceGet(p, uint64(p.rnd.Intn(keymax)))
|
||||
} else {
|
||||
instanceRelease(p, p.rnd.Intn(len(p.handles)))
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if used, cap := c.Used(), c.Capacity(); used > cap {
|
||||
t.Errorf("Used > capacity, used=%d cap=%d", used, cap)
|
||||
}
|
||||
|
||||
// Check effective objects.
|
||||
for i, p := range instances {
|
||||
if int(p.effective) < len(p.handlesMap) {
|
||||
t.Errorf("#%d effective objects < acquired handle, eo=%d ah=%d", i, p.effective, len(p.handlesMap))
|
||||
}
|
||||
}
|
||||
|
||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
||||
}
|
||||
|
||||
// First delete.
|
||||
run(runr, instances, func(p *instance) bool {
|
||||
p.wantDel = p.effective
|
||||
return p.delete
|
||||
}, func(p *instance, i int) bool {
|
||||
key := uint64(i)
|
||||
if key < keymax {
|
||||
_, wantExist := p.handlesMap[key]
|
||||
gotExist := p.ns.Delete(key, func(exist, pending bool) {
|
||||
p.delfinCalledAll++
|
||||
if exist {
|
||||
p.delfinCalledEff++
|
||||
}
|
||||
})
|
||||
if !gotExist && wantExist {
|
||||
t.Errorf("delete on NS#%d KEY#%d not found", p.nsid, key)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Second delete.
|
||||
run(runr, instances, func(p *instance) bool {
|
||||
p.delfinCalled = 0
|
||||
return p.delete
|
||||
}, func(p *instance, i int) bool {
|
||||
key := uint64(i)
|
||||
if key < keymax {
|
||||
gotExist := p.ns.Delete(key, func(exist, pending bool) {
|
||||
if exist && !pending {
|
||||
t.Errorf("delete fin on NS#%d KEY#%d exist and not pending for deletion", p.nsid, key)
|
||||
}
|
||||
p.delfinCalled++
|
||||
})
|
||||
if gotExist {
|
||||
t.Errorf("delete on NS#%d KEY#%d found", p.nsid, key)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if p.delfinCalled != keymax {
|
||||
t.Errorf("(2) #%d not all delete fin called, diff=%d", p.ns, keymax-p.delfinCalled)
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Purge.
|
||||
run(runr, instances, func(p *instance) bool {
|
||||
return p.purge
|
||||
}, func(p *instance, i int) bool {
|
||||
p.ns.Purge(func(ns, key uint64) {
|
||||
p.purgefinCalled++
|
||||
})
|
||||
return false
|
||||
})
|
||||
|
||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
||||
}
|
||||
|
||||
// Release.
|
||||
run(runr, instances, func(p *instance) bool {
|
||||
return !p.zap
|
||||
}, func(p *instance, i int) bool {
|
||||
if len(p.handles) > 0 {
|
||||
instanceRelease(p, len(p.handles)-1)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
||||
}
|
||||
|
||||
// Zap.
|
||||
run(runr, instances, func(p *instance) bool {
|
||||
return p.zap
|
||||
}, func(p *instance, i int) bool {
|
||||
p.ns.Zap()
|
||||
p.handles = nil
|
||||
p.handlesMap = nil
|
||||
return false
|
||||
})
|
||||
|
||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
||||
}
|
||||
|
||||
if notrel, used := int(cnt.created-cnt.released), c.Used(); notrel != used {
|
||||
t.Errorf("Invalid used value, want=%d got=%d", notrel, used)
|
||||
}
|
||||
|
||||
c.Purge(nil)
|
||||
|
||||
for _, p := range instances {
|
||||
if p.delete {
|
||||
if p.delfinCalledAll != keymax {
|
||||
t.Errorf("#%d not all delete fin called, purge=%v zap=%v diff=%d", p.nsid, p.purge, p.zap, keymax-p.delfinCalledAll)
|
||||
}
|
||||
if p.delfinCalledEff != p.wantDel {
|
||||
t.Errorf("#%d not all effective delete fin called, diff=%d", p.nsid, p.wantDel-p.delfinCalledEff)
|
||||
}
|
||||
if p.purge && p.purgefinCalled > 0 {
|
||||
t.Errorf("#%d some purge fin called, delete=%v zap=%v n=%d", p.nsid, p.delete, p.zap, p.purgefinCalled)
|
||||
}
|
||||
} else {
|
||||
if p.purge {
|
||||
if p.purgefinCalled != p.wantDel {
|
||||
t.Errorf("#%d not all purge fin called, delete=%v zap=%v diff=%d", p.nsid, p.delete, p.zap, p.wantDel-p.purgefinCalled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cnt.created != cnt.released {
|
||||
t.Errorf("Some cache object weren't released, created=%d released=%d", cnt.created, cnt.released)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLRUCache_Set(b *testing.B) {
|
||||
c := NewLRUCache(0)
|
||||
ns := c.GetNamespace(0)
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
set(ns, i, "", 1, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLRUCache_Get(b *testing.B) {
|
||||
c := NewLRUCache(0)
|
||||
ns := c.GetNamespace(0)
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
set(ns, i, "", 1, nil)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
ns.Get(i, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLRUCache_Get2(b *testing.B) {
|
||||
c := NewLRUCache(0)
|
||||
ns := c.GetNamespace(0)
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
set(ns, i, "", 1, nil)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
ns.Get(i, func() (charge int, value interface{}) {
|
||||
return 0, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLRUCache_Release(b *testing.B) {
|
||||
c := NewLRUCache(0)
|
||||
ns := c.GetNamespace(0)
|
||||
handles := make([]Handle, b.N)
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
handles[i] = set(ns, i, "", 1, nil)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for _, h := range handles {
|
||||
h.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLRUCache_SetRelease(b *testing.B) {
|
||||
capacity := b.N / 100
|
||||
if capacity <= 0 {
|
||||
@@ -210,7 +629,7 @@ func BenchmarkLRUCache_SetRelease(b *testing.B) {
|
||||
ns := c.GetNamespace(0)
|
||||
b.ResetTimer()
|
||||
for i := uint64(0); i < uint64(b.N); i++ {
|
||||
set(ns, i, nil, 1, nil).Release()
|
||||
set(ns, i, "", 1, nil).Release()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,10 +646,10 @@ func BenchmarkLRUCache_SetReleaseTwice(b *testing.B) {
|
||||
nb := b.N - na
|
||||
|
||||
for i := uint64(0); i < uint64(na); i++ {
|
||||
set(ns, i, nil, 1, nil).Release()
|
||||
set(ns, i, "", 1, nil).Release()
|
||||
}
|
||||
|
||||
for i := uint64(0); i < uint64(nb); i++ {
|
||||
set(ns, i, nil, 1, nil).Release()
|
||||
set(ns, i, "", 1, nil).Release()
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-246
@@ -1,246 +0,0 @@
|
||||
// Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type emptyCache struct {
|
||||
sync.Mutex
|
||||
table map[uint64]*emptyNS
|
||||
}
|
||||
|
||||
// NewEmptyCache creates a new initialized empty cache.
|
||||
func NewEmptyCache() Cache {
|
||||
return &emptyCache{
|
||||
table: make(map[uint64]*emptyNS),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *emptyCache) GetNamespace(id uint64) Namespace {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if ns, ok := c.table[id]; ok {
|
||||
return ns
|
||||
}
|
||||
|
||||
ns := &emptyNS{
|
||||
cache: c,
|
||||
id: id,
|
||||
table: make(map[uint64]*emptyNode),
|
||||
}
|
||||
c.table[id] = ns
|
||||
return ns
|
||||
}
|
||||
|
||||
func (c *emptyCache) Purge(fin PurgeFin) {
|
||||
c.Lock()
|
||||
for _, ns := range c.table {
|
||||
ns.purgeNB(fin)
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (c *emptyCache) Zap(closed bool) {
|
||||
c.Lock()
|
||||
for _, ns := range c.table {
|
||||
ns.zapNB(closed)
|
||||
}
|
||||
c.table = make(map[uint64]*emptyNS)
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (*emptyCache) SetCapacity(capacity int) {}
|
||||
|
||||
type emptyNS struct {
|
||||
cache *emptyCache
|
||||
id uint64
|
||||
table map[uint64]*emptyNode
|
||||
state nsState
|
||||
}
|
||||
|
||||
func (ns *emptyNS) Get(key uint64, setf SetFunc) (o Object, ok bool) {
|
||||
ns.cache.Lock()
|
||||
|
||||
switch ns.state {
|
||||
case nsZapped:
|
||||
ns.cache.Unlock()
|
||||
if setf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var fin func()
|
||||
ok, value, _, fin = setf()
|
||||
if ok {
|
||||
o = &fakeObject{
|
||||
value: value,
|
||||
fin: fin,
|
||||
}
|
||||
}
|
||||
return
|
||||
case nsClosed:
|
||||
ns.cache.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
n, ok := ns.table[key]
|
||||
if ok {
|
||||
n.ref++
|
||||
} else {
|
||||
if setf == nil {
|
||||
ns.cache.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var fin func()
|
||||
ok, value, _, fin = setf()
|
||||
if !ok {
|
||||
ns.cache.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
n = &emptyNode{
|
||||
ns: ns,
|
||||
key: key,
|
||||
value: value,
|
||||
setfin: fin,
|
||||
ref: 1,
|
||||
}
|
||||
ns.table[key] = n
|
||||
}
|
||||
|
||||
ns.cache.Unlock()
|
||||
o = &emptyObject{node: n}
|
||||
return
|
||||
}
|
||||
|
||||
func (ns *emptyNS) Delete(key uint64, fin DelFin) bool {
|
||||
ns.cache.Lock()
|
||||
|
||||
if ns.state != nsEffective {
|
||||
ns.cache.Unlock()
|
||||
if fin != nil {
|
||||
fin(false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
n, ok := ns.table[key]
|
||||
if !ok {
|
||||
ns.cache.Unlock()
|
||||
if fin != nil {
|
||||
fin(false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
n.delfin = fin
|
||||
ns.cache.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *emptyNS) purgeNB(fin PurgeFin) {
|
||||
if ns.state != nsEffective {
|
||||
return
|
||||
}
|
||||
for _, n := range ns.table {
|
||||
n.purgefin = fin
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *emptyNS) Purge(fin PurgeFin) {
|
||||
ns.cache.Lock()
|
||||
ns.purgeNB(fin)
|
||||
ns.cache.Unlock()
|
||||
}
|
||||
|
||||
func (ns *emptyNS) zapNB(closed bool) {
|
||||
if ns.state != nsEffective {
|
||||
return
|
||||
}
|
||||
for _, n := range ns.table {
|
||||
n.execFin()
|
||||
}
|
||||
if closed {
|
||||
ns.state = nsClosed
|
||||
} else {
|
||||
ns.state = nsZapped
|
||||
}
|
||||
ns.table = nil
|
||||
}
|
||||
|
||||
func (ns *emptyNS) Zap(closed bool) {
|
||||
ns.cache.Lock()
|
||||
ns.zapNB(closed)
|
||||
delete(ns.cache.table, ns.id)
|
||||
ns.cache.Unlock()
|
||||
}
|
||||
|
||||
type emptyNode struct {
|
||||
ns *emptyNS
|
||||
key uint64
|
||||
value interface{}
|
||||
ref int
|
||||
setfin SetFin
|
||||
delfin DelFin
|
||||
purgefin PurgeFin
|
||||
}
|
||||
|
||||
func (n *emptyNode) execFin() {
|
||||
if n.setfin != nil {
|
||||
n.setfin()
|
||||
n.setfin = nil
|
||||
}
|
||||
if n.purgefin != nil {
|
||||
n.purgefin(n.ns.id, n.key, n.delfin)
|
||||
n.delfin = nil
|
||||
n.purgefin = nil
|
||||
} else if n.delfin != nil {
|
||||
n.delfin(true)
|
||||
n.delfin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *emptyNode) evict() {
|
||||
n.ns.cache.Lock()
|
||||
n.ref--
|
||||
if n.ref == 0 {
|
||||
if n.ns.state == nsEffective {
|
||||
// Remove elem.
|
||||
delete(n.ns.table, n.key)
|
||||
// Execute finalizer.
|
||||
n.execFin()
|
||||
}
|
||||
} else if n.ref < 0 {
|
||||
panic("leveldb/cache: emptyNode: negative node reference")
|
||||
}
|
||||
n.ns.cache.Unlock()
|
||||
}
|
||||
|
||||
type emptyObject struct {
|
||||
node *emptyNode
|
||||
once uint32
|
||||
}
|
||||
|
||||
func (o *emptyObject) Value() interface{} {
|
||||
if atomic.LoadUint32(&o.once) == 0 {
|
||||
return o.node.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *emptyObject) Release() {
|
||||
if !atomic.CompareAndSwapUint32(&o.once, 0, 1) {
|
||||
return
|
||||
}
|
||||
o.node.evict()
|
||||
o.node = nil
|
||||
}
|
||||
Vendored
+448
-180
@@ -9,16 +9,24 @@ package cache
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// The LLRB implementation were taken from https://github.com/petar/GoLLRB.
|
||||
// Which contains the following header:
|
||||
//
|
||||
// Copyright 2010 Petar Maymounkov. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// lruCache represent a LRU cache state.
|
||||
type lruCache struct {
|
||||
sync.Mutex
|
||||
|
||||
recent lruNode
|
||||
table map[uint64]*lruNs
|
||||
capacity int
|
||||
size int
|
||||
mu sync.Mutex
|
||||
recent lruNode
|
||||
table map[uint64]*lruNs
|
||||
capacity int
|
||||
used, size, alive int
|
||||
}
|
||||
|
||||
// NewLRUCache creates a new initialized LRU cache with the given capacity.
|
||||
@@ -32,245 +40,415 @@ func NewLRUCache(capacity int) Cache {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *lruCache) Capacity() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.capacity
|
||||
}
|
||||
|
||||
func (c *lruCache) Used() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.used
|
||||
}
|
||||
|
||||
func (c *lruCache) Size() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.size
|
||||
}
|
||||
|
||||
func (c *lruCache) NumObjects() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.alive
|
||||
}
|
||||
|
||||
// SetCapacity set cache capacity.
|
||||
func (c *lruCache) SetCapacity(capacity int) {
|
||||
c.Lock()
|
||||
c.mu.Lock()
|
||||
c.capacity = capacity
|
||||
c.evict()
|
||||
c.Unlock()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetNamespace return namespace object for given id.
|
||||
func (c *lruCache) GetNamespace(id uint64) Namespace {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if p, ok := c.table[id]; ok {
|
||||
return p
|
||||
if ns, ok := c.table[id]; ok {
|
||||
return ns
|
||||
}
|
||||
|
||||
p := &lruNs{
|
||||
lru: c,
|
||||
id: id,
|
||||
table: make(map[uint64]*lruNode),
|
||||
ns := &lruNs{lru: c, id: id}
|
||||
c.table[id] = ns
|
||||
return ns
|
||||
}
|
||||
|
||||
func (c *lruCache) ZapNamespace(id uint64) {
|
||||
c.mu.Lock()
|
||||
if ns, exist := c.table[id]; exist {
|
||||
ns.zapNB()
|
||||
delete(c.table, id)
|
||||
}
|
||||
c.table[id] = p
|
||||
return p
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *lruCache) PurgeNamespace(id uint64, fin PurgeFin) {
|
||||
c.mu.Lock()
|
||||
if ns, exist := c.table[id]; exist {
|
||||
ns.purgeNB(fin)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Purge purge entire cache.
|
||||
func (c *lruCache) Purge(fin PurgeFin) {
|
||||
c.Lock()
|
||||
c.mu.Lock()
|
||||
for _, ns := range c.table {
|
||||
ns.purgeNB(fin)
|
||||
}
|
||||
c.Unlock()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *lruCache) Zap(closed bool) {
|
||||
c.Lock()
|
||||
func (c *lruCache) Zap() {
|
||||
c.mu.Lock()
|
||||
for _, ns := range c.table {
|
||||
ns.zapNB(closed)
|
||||
ns.zapNB()
|
||||
}
|
||||
c.table = make(map[uint64]*lruNs)
|
||||
c.Unlock()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *lruCache) evict() {
|
||||
top := &c.recent
|
||||
for n := c.recent.rPrev; c.size > c.capacity && n != top; {
|
||||
for n := c.recent.rPrev; c.used > c.capacity && n != top; {
|
||||
if n.state != nodeEffective {
|
||||
panic("evicting non effective node")
|
||||
}
|
||||
n.state = nodeEvicted
|
||||
n.rRemove()
|
||||
n.evictNB()
|
||||
c.size -= n.charge
|
||||
n.derefNB()
|
||||
c.used -= n.charge
|
||||
n = c.recent.rPrev
|
||||
}
|
||||
}
|
||||
|
||||
type lruNs struct {
|
||||
lru *lruCache
|
||||
id uint64
|
||||
table map[uint64]*lruNode
|
||||
state nsState
|
||||
lru *lruCache
|
||||
id uint64
|
||||
rbRoot *lruNode
|
||||
state nsState
|
||||
}
|
||||
|
||||
func (ns *lruNs) Get(key uint64, setf SetFunc) (o Object, ok bool) {
|
||||
lru := ns.lru
|
||||
lru.Lock()
|
||||
|
||||
switch ns.state {
|
||||
case nsZapped:
|
||||
lru.Unlock()
|
||||
if setf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var fin func()
|
||||
ok, value, _, fin = setf()
|
||||
if ok {
|
||||
o = &fakeObject{
|
||||
value: value,
|
||||
fin: fin,
|
||||
}
|
||||
}
|
||||
return
|
||||
case nsClosed:
|
||||
lru.Unlock()
|
||||
return
|
||||
func (ns *lruNs) rbGetOrCreateNode(h *lruNode, key uint64) (hn, n *lruNode) {
|
||||
if h == nil {
|
||||
n = &lruNode{ns: ns, key: key}
|
||||
return n, n
|
||||
}
|
||||
|
||||
n, ok := ns.table[key]
|
||||
if ok {
|
||||
switch n.state {
|
||||
case nodeEvicted:
|
||||
// Insert to recent list.
|
||||
n.state = nodeEffective
|
||||
n.ref++
|
||||
lru.size += n.charge
|
||||
lru.evict()
|
||||
fallthrough
|
||||
case nodeEffective:
|
||||
// Bump to front
|
||||
n.rRemove()
|
||||
n.rInsert(&lru.recent)
|
||||
if key < h.key {
|
||||
hn, n = ns.rbGetOrCreateNode(h.rbLeft, key)
|
||||
if hn != nil {
|
||||
h.rbLeft = hn
|
||||
} else {
|
||||
return nil, n
|
||||
}
|
||||
} else if key > h.key {
|
||||
hn, n = ns.rbGetOrCreateNode(h.rbRight, key)
|
||||
if hn != nil {
|
||||
h.rbRight = hn
|
||||
} else {
|
||||
return nil, n
|
||||
}
|
||||
n.ref++
|
||||
} else {
|
||||
if setf == nil {
|
||||
lru.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var charge int
|
||||
var fin func()
|
||||
ok, value, charge, fin = setf()
|
||||
if !ok {
|
||||
lru.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
n = &lruNode{
|
||||
ns: ns,
|
||||
key: key,
|
||||
value: value,
|
||||
charge: charge,
|
||||
setfin: fin,
|
||||
ref: 2,
|
||||
}
|
||||
ns.table[key] = n
|
||||
n.rInsert(&lru.recent)
|
||||
|
||||
lru.size += charge
|
||||
lru.evict()
|
||||
return nil, h
|
||||
}
|
||||
|
||||
lru.Unlock()
|
||||
o = &lruObject{node: n}
|
||||
return
|
||||
if rbIsRed(h.rbRight) && !rbIsRed(h.rbLeft) {
|
||||
h = rbRotLeft(h)
|
||||
}
|
||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
|
||||
h = rbRotRight(h)
|
||||
}
|
||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
|
||||
rbFlip(h)
|
||||
}
|
||||
return h, n
|
||||
}
|
||||
|
||||
func (ns *lruNs) getOrCreateNode(key uint64) *lruNode {
|
||||
hn, n := ns.rbGetOrCreateNode(ns.rbRoot, key)
|
||||
if hn != nil {
|
||||
ns.rbRoot = hn
|
||||
ns.rbRoot.rbBlack = true
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (ns *lruNs) rbGetNode(key uint64) *lruNode {
|
||||
h := ns.rbRoot
|
||||
for h != nil {
|
||||
switch {
|
||||
case key < h.key:
|
||||
h = h.rbLeft
|
||||
case key > h.key:
|
||||
h = h.rbRight
|
||||
default:
|
||||
return h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *lruNs) getNode(key uint64) *lruNode {
|
||||
return ns.rbGetNode(key)
|
||||
}
|
||||
|
||||
func (ns *lruNs) rbDeleteNode(h *lruNode, key uint64) *lruNode {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if key < h.key {
|
||||
if h.rbLeft == nil { // key not present. Nothing to delete
|
||||
return h
|
||||
}
|
||||
if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
|
||||
h = rbMoveLeft(h)
|
||||
}
|
||||
h.rbLeft = ns.rbDeleteNode(h.rbLeft, key)
|
||||
} else {
|
||||
if rbIsRed(h.rbLeft) {
|
||||
h = rbRotRight(h)
|
||||
}
|
||||
// If @key equals @h.key and no right children at @h
|
||||
if h.key == key && h.rbRight == nil {
|
||||
return nil
|
||||
}
|
||||
if h.rbRight != nil && !rbIsRed(h.rbRight) && !rbIsRed(h.rbRight.rbLeft) {
|
||||
h = rbMoveRight(h)
|
||||
}
|
||||
// If @key equals @h.key, and (from above) 'h.Right != nil'
|
||||
if h.key == key {
|
||||
var x *lruNode
|
||||
h.rbRight, x = rbDeleteMin(h.rbRight)
|
||||
if x == nil {
|
||||
panic("logic")
|
||||
}
|
||||
x.rbLeft, h.rbLeft = h.rbLeft, nil
|
||||
x.rbRight, h.rbRight = h.rbRight, nil
|
||||
x.rbBlack = h.rbBlack
|
||||
h = x
|
||||
} else { // Else, @key is bigger than @h.key
|
||||
h.rbRight = ns.rbDeleteNode(h.rbRight, key)
|
||||
}
|
||||
}
|
||||
|
||||
return rbFixup(h)
|
||||
}
|
||||
|
||||
func (ns *lruNs) deleteNode(key uint64) {
|
||||
ns.rbRoot = ns.rbDeleteNode(ns.rbRoot, key)
|
||||
if ns.rbRoot != nil {
|
||||
ns.rbRoot.rbBlack = true
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *lruNs) rbIterateNodes(h *lruNode, pivot uint64, iter func(n *lruNode) bool) bool {
|
||||
if h == nil {
|
||||
return true
|
||||
}
|
||||
if h.key >= pivot {
|
||||
if !ns.rbIterateNodes(h.rbLeft, pivot, iter) {
|
||||
return false
|
||||
}
|
||||
if !iter(h) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return ns.rbIterateNodes(h.rbRight, pivot, iter)
|
||||
}
|
||||
|
||||
func (ns *lruNs) iterateNodes(iter func(n *lruNode) bool) {
|
||||
ns.rbIterateNodes(ns.rbRoot, 0, iter)
|
||||
}
|
||||
|
||||
func (ns *lruNs) Get(key uint64, setf SetFunc) Handle {
|
||||
ns.lru.mu.Lock()
|
||||
defer ns.lru.mu.Unlock()
|
||||
|
||||
if ns.state != nsEffective {
|
||||
return nil
|
||||
}
|
||||
|
||||
var n *lruNode
|
||||
if setf == nil {
|
||||
n = ns.getNode(key)
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
n = ns.getOrCreateNode(key)
|
||||
}
|
||||
switch n.state {
|
||||
case nodeZero:
|
||||
charge, value := setf()
|
||||
if value == nil {
|
||||
ns.deleteNode(key)
|
||||
return nil
|
||||
}
|
||||
if charge < 0 {
|
||||
charge = 0
|
||||
}
|
||||
|
||||
n.value = value
|
||||
n.charge = charge
|
||||
n.state = nodeEvicted
|
||||
|
||||
ns.lru.size += charge
|
||||
ns.lru.alive++
|
||||
|
||||
fallthrough
|
||||
case nodeEvicted:
|
||||
if n.charge == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Insert to recent list.
|
||||
n.state = nodeEffective
|
||||
n.ref++
|
||||
ns.lru.used += n.charge
|
||||
ns.lru.evict()
|
||||
|
||||
fallthrough
|
||||
case nodeEffective:
|
||||
// Bump to front.
|
||||
n.rRemove()
|
||||
n.rInsert(&ns.lru.recent)
|
||||
case nodeDeleted:
|
||||
// Do nothing.
|
||||
default:
|
||||
panic("invalid state")
|
||||
}
|
||||
n.ref++
|
||||
|
||||
return &lruHandle{node: n}
|
||||
}
|
||||
|
||||
func (ns *lruNs) Delete(key uint64, fin DelFin) bool {
|
||||
lru := ns.lru
|
||||
lru.Lock()
|
||||
ns.lru.mu.Lock()
|
||||
defer ns.lru.mu.Unlock()
|
||||
|
||||
if ns.state != nsEffective {
|
||||
lru.Unlock()
|
||||
if fin != nil {
|
||||
fin(false)
|
||||
fin(false, false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
n, ok := ns.table[key]
|
||||
if !ok {
|
||||
lru.Unlock()
|
||||
n := ns.getNode(key)
|
||||
if n == nil {
|
||||
if fin != nil {
|
||||
fin(false)
|
||||
fin(false, false)
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
n.delfin = fin
|
||||
switch n.state {
|
||||
case nodeRemoved:
|
||||
lru.Unlock()
|
||||
return false
|
||||
case nodeEffective:
|
||||
lru.size -= n.charge
|
||||
ns.lru.used -= n.charge
|
||||
n.state = nodeDeleted
|
||||
n.delfin = fin
|
||||
n.rRemove()
|
||||
n.evictNB()
|
||||
n.derefNB()
|
||||
case nodeEvicted:
|
||||
n.state = nodeDeleted
|
||||
n.delfin = fin
|
||||
case nodeDeleted:
|
||||
if fin != nil {
|
||||
fin(true, true)
|
||||
}
|
||||
return false
|
||||
default:
|
||||
panic("invalid state")
|
||||
}
|
||||
n.state = nodeRemoved
|
||||
|
||||
lru.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *lruNs) purgeNB(fin PurgeFin) {
|
||||
lru := ns.lru
|
||||
if ns.state != nsEffective {
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range ns.table {
|
||||
n.purgefin = fin
|
||||
if n.state == nodeEffective {
|
||||
lru.size -= n.charge
|
||||
n.rRemove()
|
||||
n.evictNB()
|
||||
if ns.state == nsEffective {
|
||||
var nodes []*lruNode
|
||||
ns.iterateNodes(func(n *lruNode) bool {
|
||||
nodes = append(nodes, n)
|
||||
return true
|
||||
})
|
||||
for _, n := range nodes {
|
||||
switch n.state {
|
||||
case nodeEffective:
|
||||
ns.lru.used -= n.charge
|
||||
n.state = nodeDeleted
|
||||
n.purgefin = fin
|
||||
n.rRemove()
|
||||
n.derefNB()
|
||||
case nodeEvicted:
|
||||
n.state = nodeDeleted
|
||||
n.purgefin = fin
|
||||
case nodeDeleted:
|
||||
default:
|
||||
panic("invalid state")
|
||||
}
|
||||
}
|
||||
n.state = nodeRemoved
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *lruNs) Purge(fin PurgeFin) {
|
||||
ns.lru.Lock()
|
||||
ns.lru.mu.Lock()
|
||||
ns.purgeNB(fin)
|
||||
ns.lru.Unlock()
|
||||
ns.lru.mu.Unlock()
|
||||
}
|
||||
|
||||
func (ns *lruNs) zapNB(closed bool) {
|
||||
lru := ns.lru
|
||||
if ns.state != nsEffective {
|
||||
return
|
||||
}
|
||||
|
||||
if closed {
|
||||
ns.state = nsClosed
|
||||
} else {
|
||||
func (ns *lruNs) zapNB() {
|
||||
if ns.state == nsEffective {
|
||||
ns.state = nsZapped
|
||||
|
||||
ns.iterateNodes(func(n *lruNode) bool {
|
||||
if n.state == nodeEffective {
|
||||
ns.lru.used -= n.charge
|
||||
n.rRemove()
|
||||
}
|
||||
ns.lru.size -= n.charge
|
||||
n.state = nodeDeleted
|
||||
n.fin()
|
||||
|
||||
return true
|
||||
})
|
||||
ns.rbRoot = nil
|
||||
}
|
||||
for _, n := range ns.table {
|
||||
if n.state == nodeEffective {
|
||||
lru.size -= n.charge
|
||||
n.rRemove()
|
||||
}
|
||||
n.state = nodeRemoved
|
||||
n.execFin()
|
||||
}
|
||||
ns.table = nil
|
||||
}
|
||||
|
||||
func (ns *lruNs) Zap(closed bool) {
|
||||
ns.lru.Lock()
|
||||
ns.zapNB(closed)
|
||||
func (ns *lruNs) Zap() {
|
||||
ns.lru.mu.Lock()
|
||||
ns.zapNB()
|
||||
delete(ns.lru.table, ns.id)
|
||||
ns.lru.Unlock()
|
||||
ns.lru.mu.Unlock()
|
||||
}
|
||||
|
||||
type lruNode struct {
|
||||
ns *lruNs
|
||||
|
||||
rNext, rPrev *lruNode
|
||||
rNext, rPrev *lruNode
|
||||
rbLeft, rbRight *lruNode
|
||||
rbBlack bool
|
||||
|
||||
key uint64
|
||||
value interface{}
|
||||
charge int
|
||||
ref int
|
||||
state nodeState
|
||||
setfin SetFin
|
||||
delfin DelFin
|
||||
purgefin PurgeFin
|
||||
}
|
||||
@@ -284,7 +462,6 @@ func (n *lruNode) rInsert(at *lruNode) {
|
||||
}
|
||||
|
||||
func (n *lruNode) rRemove() bool {
|
||||
// only remove if not already removed
|
||||
if n.rPrev == nil {
|
||||
return false
|
||||
}
|
||||
@@ -297,58 +474,149 @@ func (n *lruNode) rRemove() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *lruNode) execFin() {
|
||||
if n.setfin != nil {
|
||||
n.setfin()
|
||||
n.setfin = nil
|
||||
func (n *lruNode) fin() {
|
||||
if r, ok := n.value.(util.Releaser); ok {
|
||||
r.Release()
|
||||
}
|
||||
if n.purgefin != nil {
|
||||
n.purgefin(n.ns.id, n.key, n.delfin)
|
||||
n.delfin = nil
|
||||
if n.delfin != nil {
|
||||
panic("conflicting delete and purge fin")
|
||||
}
|
||||
n.purgefin(n.ns.id, n.key)
|
||||
n.purgefin = nil
|
||||
} else if n.delfin != nil {
|
||||
n.delfin(true)
|
||||
n.delfin(true, false)
|
||||
n.delfin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *lruNode) evictNB() {
|
||||
func (n *lruNode) derefNB() {
|
||||
n.ref--
|
||||
if n.ref == 0 {
|
||||
if n.ns.state == nsEffective {
|
||||
// remove elem
|
||||
delete(n.ns.table, n.key)
|
||||
// execute finalizer
|
||||
n.execFin()
|
||||
// Remove elemement.
|
||||
n.ns.deleteNode(n.key)
|
||||
n.ns.lru.size -= n.charge
|
||||
n.ns.lru.alive--
|
||||
n.fin()
|
||||
}
|
||||
n.value = nil
|
||||
} else if n.ref < 0 {
|
||||
panic("leveldb/cache: lruCache: negative node reference")
|
||||
}
|
||||
}
|
||||
|
||||
func (n *lruNode) evict() {
|
||||
n.ns.lru.Lock()
|
||||
n.evictNB()
|
||||
n.ns.lru.Unlock()
|
||||
func (n *lruNode) deref() {
|
||||
n.ns.lru.mu.Lock()
|
||||
n.derefNB()
|
||||
n.ns.lru.mu.Unlock()
|
||||
}
|
||||
|
||||
type lruObject struct {
|
||||
type lruHandle struct {
|
||||
node *lruNode
|
||||
once uint32
|
||||
}
|
||||
|
||||
func (o *lruObject) Value() interface{} {
|
||||
if atomic.LoadUint32(&o.once) == 0 {
|
||||
return o.node.value
|
||||
func (h *lruHandle) Value() interface{} {
|
||||
if atomic.LoadUint32(&h.once) == 0 {
|
||||
return h.node.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *lruObject) Release() {
|
||||
if !atomic.CompareAndSwapUint32(&o.once, 0, 1) {
|
||||
func (h *lruHandle) Release() {
|
||||
if !atomic.CompareAndSwapUint32(&h.once, 0, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
o.node.evict()
|
||||
o.node = nil
|
||||
h.node.deref()
|
||||
h.node = nil
|
||||
}
|
||||
|
||||
func rbIsRed(h *lruNode) bool {
|
||||
if h == nil {
|
||||
return false
|
||||
}
|
||||
return !h.rbBlack
|
||||
}
|
||||
|
||||
func rbRotLeft(h *lruNode) *lruNode {
|
||||
x := h.rbRight
|
||||
if x.rbBlack {
|
||||
panic("rotating a black link")
|
||||
}
|
||||
h.rbRight = x.rbLeft
|
||||
x.rbLeft = h
|
||||
x.rbBlack = h.rbBlack
|
||||
h.rbBlack = false
|
||||
return x
|
||||
}
|
||||
|
||||
func rbRotRight(h *lruNode) *lruNode {
|
||||
x := h.rbLeft
|
||||
if x.rbBlack {
|
||||
panic("rotating a black link")
|
||||
}
|
||||
h.rbLeft = x.rbRight
|
||||
x.rbRight = h
|
||||
x.rbBlack = h.rbBlack
|
||||
h.rbBlack = false
|
||||
return x
|
||||
}
|
||||
|
||||
func rbFlip(h *lruNode) {
|
||||
h.rbBlack = !h.rbBlack
|
||||
h.rbLeft.rbBlack = !h.rbLeft.rbBlack
|
||||
h.rbRight.rbBlack = !h.rbRight.rbBlack
|
||||
}
|
||||
|
||||
func rbMoveLeft(h *lruNode) *lruNode {
|
||||
rbFlip(h)
|
||||
if rbIsRed(h.rbRight.rbLeft) {
|
||||
h.rbRight = rbRotRight(h.rbRight)
|
||||
h = rbRotLeft(h)
|
||||
rbFlip(h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func rbMoveRight(h *lruNode) *lruNode {
|
||||
rbFlip(h)
|
||||
if rbIsRed(h.rbLeft.rbLeft) {
|
||||
h = rbRotRight(h)
|
||||
rbFlip(h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func rbFixup(h *lruNode) *lruNode {
|
||||
if rbIsRed(h.rbRight) {
|
||||
h = rbRotLeft(h)
|
||||
}
|
||||
|
||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
|
||||
h = rbRotRight(h)
|
||||
}
|
||||
|
||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
|
||||
rbFlip(h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func rbDeleteMin(h *lruNode) (hn, n *lruNode) {
|
||||
if h == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if h.rbLeft == nil {
|
||||
return nil, h
|
||||
}
|
||||
|
||||
if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
|
||||
h = rbMoveLeft(h)
|
||||
}
|
||||
|
||||
h.rbLeft, n = rbDeleteMin(h.rbLeft)
|
||||
|
||||
return rbFixup(h), n
|
||||
}
|
||||
|
||||
+33
-19
@@ -9,34 +9,48 @@ package leveldb
|
||||
import "github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
|
||||
type iComparer struct {
|
||||
cmp comparer.Comparer
|
||||
ucmp comparer.Comparer
|
||||
}
|
||||
|
||||
func (p *iComparer) Name() string {
|
||||
return p.cmp.Name()
|
||||
func (icmp *iComparer) uName() string {
|
||||
return icmp.ucmp.Name()
|
||||
}
|
||||
|
||||
func (p *iComparer) Compare(a, b []byte) int {
|
||||
ia, ib := iKey(a), iKey(b)
|
||||
r := p.cmp.Compare(ia.ukey(), ib.ukey())
|
||||
if r == 0 {
|
||||
an, bn := ia.num(), ib.num()
|
||||
if an > bn {
|
||||
r = -1
|
||||
} else if an < bn {
|
||||
r = 1
|
||||
func (icmp *iComparer) uCompare(a, b []byte) int {
|
||||
return icmp.ucmp.Compare(a, b)
|
||||
}
|
||||
|
||||
func (icmp *iComparer) uSeparator(dst, a, b []byte) []byte {
|
||||
return icmp.ucmp.Separator(dst, a, b)
|
||||
}
|
||||
|
||||
func (icmp *iComparer) uSuccessor(dst, b []byte) []byte {
|
||||
return icmp.ucmp.Successor(dst, b)
|
||||
}
|
||||
|
||||
func (icmp *iComparer) Name() string {
|
||||
return icmp.uName()
|
||||
}
|
||||
|
||||
func (icmp *iComparer) Compare(a, b []byte) int {
|
||||
x := icmp.ucmp.Compare(iKey(a).ukey(), iKey(b).ukey())
|
||||
if x == 0 {
|
||||
if m, n := iKey(a).num(), iKey(b).num(); m > n {
|
||||
x = -1
|
||||
} else if m < n {
|
||||
x = 1
|
||||
}
|
||||
}
|
||||
return r
|
||||
return x
|
||||
}
|
||||
|
||||
func (p *iComparer) Separator(dst, a, b []byte) []byte {
|
||||
func (icmp *iComparer) Separator(dst, a, b []byte) []byte {
|
||||
ua, ub := iKey(a).ukey(), iKey(b).ukey()
|
||||
dst = p.cmp.Separator(dst, ua, ub)
|
||||
dst = icmp.ucmp.Separator(dst, ua, ub)
|
||||
if dst == nil {
|
||||
return nil
|
||||
}
|
||||
if len(dst) < len(ua) && p.cmp.Compare(ua, dst) < 0 {
|
||||
if len(dst) < len(ua) && icmp.uCompare(ua, dst) < 0 {
|
||||
dst = append(dst, kMaxNumBytes...)
|
||||
} else {
|
||||
// Did not close possibilities that n maybe longer than len(ub).
|
||||
@@ -45,13 +59,13 @@ func (p *iComparer) Separator(dst, a, b []byte) []byte {
|
||||
return dst
|
||||
}
|
||||
|
||||
func (p *iComparer) Successor(dst, b []byte) []byte {
|
||||
func (icmp *iComparer) Successor(dst, b []byte) []byte {
|
||||
ub := iKey(b).ukey()
|
||||
dst = p.cmp.Successor(dst, ub)
|
||||
dst = icmp.ucmp.Successor(dst, ub)
|
||||
if dst == nil {
|
||||
return nil
|
||||
}
|
||||
if len(dst) < len(ub) && p.cmp.Compare(ub, dst) < 0 {
|
||||
if len(dst) < len(ub) && icmp.uCompare(ub, dst) < 0 {
|
||||
dst = append(dst, kMaxNumBytes...)
|
||||
} else {
|
||||
// Did not close possibilities that n maybe longer than len(ub).
|
||||
|
||||
+58
@@ -176,6 +176,18 @@ func (h *dbCorruptHarness) removeAll(ft storage.FileType) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbCorruptHarness) removeOne(ft storage.FileType) {
|
||||
ff, err := h.stor.GetFiles(ft)
|
||||
if err != nil {
|
||||
h.t.Fatal("get files: ", err)
|
||||
}
|
||||
f := ff[rand.Intn(len(ff))]
|
||||
h.t.Logf("removing file @%d", f.Num())
|
||||
if err := f.Remove(); err != nil {
|
||||
h.t.Error("remove file: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbCorruptHarness) check(min, max int) {
|
||||
p := &h.dbHarness
|
||||
t := p.t
|
||||
@@ -386,6 +398,33 @@ func TestCorruptDB_UnrelatedKeys(t *testing.T) {
|
||||
h.close()
|
||||
}
|
||||
|
||||
func TestCorruptDB_Level0NewerFileHasOlderSeqnum(t *testing.T) {
|
||||
h := newDbCorruptHarness(t)
|
||||
|
||||
h.put("a", "v1")
|
||||
h.put("b", "v1")
|
||||
h.compactMem()
|
||||
h.put("a", "v2")
|
||||
h.put("b", "v2")
|
||||
h.compactMem()
|
||||
h.put("a", "v3")
|
||||
h.put("b", "v3")
|
||||
h.compactMem()
|
||||
h.put("c", "v0")
|
||||
h.put("d", "v0")
|
||||
h.compactMem()
|
||||
h.compactRangeAt(1, "", "")
|
||||
h.closeDB()
|
||||
|
||||
h.recover()
|
||||
h.getVal("a", "v3")
|
||||
h.getVal("b", "v3")
|
||||
h.getVal("c", "v0")
|
||||
h.getVal("d", "v0")
|
||||
|
||||
h.close()
|
||||
}
|
||||
|
||||
func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) {
|
||||
h := newDbCorruptHarness(t)
|
||||
|
||||
@@ -412,3 +451,22 @@ func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) {
|
||||
|
||||
h.close()
|
||||
}
|
||||
|
||||
func TestCorruptDB_MissingTableFiles(t *testing.T) {
|
||||
h := newDbCorruptHarness(t)
|
||||
|
||||
h.put("a", "v1")
|
||||
h.put("b", "v1")
|
||||
h.compactMem()
|
||||
h.put("c", "v2")
|
||||
h.put("d", "v2")
|
||||
h.compactMem()
|
||||
h.put("e", "v3")
|
||||
h.put("f", "v3")
|
||||
h.closeDB()
|
||||
|
||||
h.removeOne(storage.TypeTable)
|
||||
h.openAssert(false)
|
||||
|
||||
h.close()
|
||||
}
|
||||
|
||||
+263
-226
@@ -14,6 +14,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
@@ -25,56 +26,54 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrSnapshotReleased = errors.New("leveldb: snapshot released")
|
||||
ErrIterReleased = errors.New("leveldb: iterator released")
|
||||
ErrClosed = errors.New("leveldb: closed")
|
||||
)
|
||||
|
||||
// DB is a LevelDB database.
|
||||
type DB struct {
|
||||
// Need 64-bit alignment.
|
||||
seq uint64
|
||||
|
||||
// Session.
|
||||
s *session
|
||||
|
||||
// MemDB
|
||||
// MemDB.
|
||||
memMu sync.RWMutex
|
||||
mem *memdb.DB
|
||||
frozenMem *memdb.DB
|
||||
memPool chan *memdb.DB
|
||||
mem, frozenMem *memDB
|
||||
journal *journal.Writer
|
||||
journalWriter storage.Writer
|
||||
journalFile storage.File
|
||||
frozenJournalFile storage.File
|
||||
frozenSeq uint64
|
||||
|
||||
// Snapshot
|
||||
// Snapshot.
|
||||
snapsMu sync.Mutex
|
||||
snapsRoot snapshotElement
|
||||
|
||||
// Write
|
||||
writeCh chan *Batch
|
||||
writeMergedCh chan bool
|
||||
writeLockCh chan struct{}
|
||||
writeAckCh chan error
|
||||
journalCh chan *Batch
|
||||
journalAckCh chan error
|
||||
// Stats.
|
||||
aliveSnaps, aliveIters int32
|
||||
|
||||
// Compaction
|
||||
compCh chan chan<- struct{}
|
||||
compMemCh chan chan<- struct{}
|
||||
compMemAckCh chan struct{}
|
||||
compReqCh chan *cReq
|
||||
compErrCh chan error
|
||||
compErrSetCh chan error
|
||||
compStats [kNumLevels]cStats
|
||||
// Write.
|
||||
writeC chan *Batch
|
||||
writeMergedC chan bool
|
||||
writeLockC chan struct{}
|
||||
writeAckC chan error
|
||||
journalC chan *Batch
|
||||
journalAckC chan error
|
||||
|
||||
// Close
|
||||
closeWg sync.WaitGroup
|
||||
closeCh chan struct{}
|
||||
closed uint32
|
||||
closer io.Closer
|
||||
// Compaction.
|
||||
tcompCmdC chan cCmd
|
||||
tcompPauseC chan chan<- struct{}
|
||||
tcompTriggerC chan struct{}
|
||||
mcompCmdC chan cCmd
|
||||
mcompTriggerC chan struct{}
|
||||
compErrC chan error
|
||||
compErrSetC chan error
|
||||
compStats [kNumLevels]cStats
|
||||
|
||||
// Close.
|
||||
closeW sync.WaitGroup
|
||||
closeC chan struct{}
|
||||
closed uint32
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func openDB(s *session) (*DB, error) {
|
||||
@@ -84,42 +83,50 @@ func openDB(s *session) (*DB, error) {
|
||||
s: s,
|
||||
// Initial sequence
|
||||
seq: s.stSeq,
|
||||
// MemDB
|
||||
memPool: make(chan *memdb.DB, 1),
|
||||
// Write
|
||||
writeCh: make(chan *Batch),
|
||||
writeMergedCh: make(chan bool),
|
||||
writeLockCh: make(chan struct{}, 1),
|
||||
writeAckCh: make(chan error),
|
||||
journalCh: make(chan *Batch),
|
||||
journalAckCh: make(chan error),
|
||||
writeC: make(chan *Batch),
|
||||
writeMergedC: make(chan bool),
|
||||
writeLockC: make(chan struct{}, 1),
|
||||
writeAckC: make(chan error),
|
||||
journalC: make(chan *Batch),
|
||||
journalAckC: make(chan error),
|
||||
// Compaction
|
||||
compCh: make(chan chan<- struct{}, 1),
|
||||
compMemCh: make(chan chan<- struct{}, 1),
|
||||
compMemAckCh: make(chan struct{}, 1),
|
||||
compReqCh: make(chan *cReq),
|
||||
compErrCh: make(chan error),
|
||||
compErrSetCh: make(chan error),
|
||||
tcompCmdC: make(chan cCmd),
|
||||
tcompPauseC: make(chan chan<- struct{}),
|
||||
tcompTriggerC: make(chan struct{}, 1),
|
||||
mcompCmdC: make(chan cCmd),
|
||||
mcompTriggerC: make(chan struct{}, 1),
|
||||
compErrC: make(chan error),
|
||||
compErrSetC: make(chan error),
|
||||
// Close
|
||||
closeCh: make(chan struct{}),
|
||||
closeC: make(chan struct{}),
|
||||
}
|
||||
db.initSnapshot()
|
||||
db.compMemAckCh <- struct{}{}
|
||||
|
||||
if err := db.recoverJournal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove any obsolete files.
|
||||
if err := db.cleanFiles(); err != nil {
|
||||
if err := db.checkAndCleanFiles(); err != nil {
|
||||
// Close journal.
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't include compaction error goroutine into wait group.
|
||||
go db.compactionError()
|
||||
|
||||
db.closeWg.Add(2)
|
||||
go db.compaction()
|
||||
go db.writeJournal()
|
||||
db.wakeCompaction(0)
|
||||
db.closeW.Add(3)
|
||||
go db.tCompaction()
|
||||
go db.mCompaction()
|
||||
go db.jWriter()
|
||||
go db.mpoolDrain()
|
||||
|
||||
s.logf("db@open done T·%v", time.Since(start))
|
||||
|
||||
@@ -132,13 +139,14 @@ func openDB(s *session) (*DB, error) {
|
||||
// Also, if ErrorIfExist is true and the DB exist Open will returns
|
||||
// os.ErrExist error.
|
||||
//
|
||||
// Open will return an error with type of ErrManifest if manifest file
|
||||
// is missing or corrupted. Missing or corrupted manifest file can be
|
||||
// recovered with Recover function.
|
||||
// Open will return an error with type of ErrCorrupted if corruption
|
||||
// detected in the DB. Corrupted DB can be recovered with Recover
|
||||
// function.
|
||||
//
|
||||
// The returned DB instance is goroutine-safe.
|
||||
// The DB must be closed after use, by calling Close method.
|
||||
func Open(p storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
s, err := newSession(p, o)
|
||||
func Open(stor storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
s, err := newSession(stor, o)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -174,10 +182,11 @@ func Open(p storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
// OpenFile uses standard file-system backed storage implementation as
|
||||
// desribed in the leveldb/storage package.
|
||||
//
|
||||
// OpenFile will return an error with type of ErrManifest if manifest file
|
||||
// is missing or corrupted. Missing or corrupted manifest file can be
|
||||
// recovered with Recover function.
|
||||
// OpenFile will return an error with type of ErrCorrupted if corruption
|
||||
// detected in the DB. Corrupted DB can be recovered with Recover
|
||||
// function.
|
||||
//
|
||||
// The returned DB instance is goroutine-safe.
|
||||
// The DB must be closed after use, by calling Close method.
|
||||
func OpenFile(path string, o *opt.Options) (db *DB, err error) {
|
||||
stor, err := storage.OpenFile(path)
|
||||
@@ -198,9 +207,10 @@ func OpenFile(path string, o *opt.Options) (db *DB, err error) {
|
||||
// The DB must already exist or it will returns an error.
|
||||
// Also, Recover will ignore ErrorIfMissing and ErrorIfExist options.
|
||||
//
|
||||
// The returned DB instance is goroutine-safe.
|
||||
// The DB must be closed after use, by calling Close method.
|
||||
func Recover(p storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
s, err := newSession(p, o)
|
||||
func Recover(stor storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
s, err := newSession(stor, o)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -226,6 +236,7 @@ func Recover(p storage.Storage, o *opt.Options) (db *DB, err error) {
|
||||
// RecoverFile uses standard file-system backed storage implementation as desribed
|
||||
// in the leveldb/storage package.
|
||||
//
|
||||
// The returned DB instance is goroutine-safe.
|
||||
// The DB must be closed after use, by calling Close method.
|
||||
func RecoverFile(path string, o *opt.Options) (db *DB, err error) {
|
||||
stor, err := storage.OpenFile(path)
|
||||
@@ -242,16 +253,18 @@ func RecoverFile(path string, o *opt.Options) (db *DB, err error) {
|
||||
}
|
||||
|
||||
func recoverTable(s *session, o *opt.Options) error {
|
||||
ff0, err := s.getFiles(storage.TypeTable)
|
||||
// Get all tables and sort it by file number.
|
||||
tableFiles_, err := s.getFiles(storage.TypeTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ff1 := files(ff0)
|
||||
ff1.sort()
|
||||
tableFiles := files(tableFiles_)
|
||||
tableFiles.sort()
|
||||
|
||||
var mSeq uint64
|
||||
var good, corrupted int
|
||||
rec := new(sessionRecord)
|
||||
bpool := util.NewBufferPool(o.GetBlockSize() + 5)
|
||||
buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
|
||||
tmp = s.newTemp()
|
||||
writer, err := tmp.Create()
|
||||
@@ -265,8 +278,9 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
tmp = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// Copy entries.
|
||||
tw := table.NewWriter(writer, o)
|
||||
// Copy records.
|
||||
for iter.Next() {
|
||||
key := iter.Key()
|
||||
if validIkey(key) {
|
||||
@@ -298,20 +312,23 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Get file size.
|
||||
size, err := reader.Seek(0, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tSeq uint64
|
||||
var tgood, tcorrupted, blockerr int
|
||||
var min, max []byte
|
||||
tr := table.NewReader(reader, size, nil, o)
|
||||
var imin, imax []byte
|
||||
tr := table.NewReader(reader, size, nil, bpool, o)
|
||||
iter := tr.NewIterator(nil, nil)
|
||||
iter.(iterator.ErrorCallbackSetter).SetErrorCallback(func(err error) {
|
||||
s.logf("table@recovery found error @%d %q", file.Num(), err)
|
||||
blockerr++
|
||||
})
|
||||
|
||||
// Scan the table.
|
||||
for iter.Next() {
|
||||
key := iter.Key()
|
||||
@@ -324,16 +341,17 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
if seq > tSeq {
|
||||
tSeq = seq
|
||||
}
|
||||
if min == nil {
|
||||
min = append([]byte{}, key...)
|
||||
if imin == nil {
|
||||
imin = append([]byte{}, key...)
|
||||
}
|
||||
max = append(max[:0], key...)
|
||||
imax = append(imax[:0], key...)
|
||||
}
|
||||
if err := iter.Error(); err != nil {
|
||||
iter.Release()
|
||||
return err
|
||||
}
|
||||
iter.Release()
|
||||
|
||||
if tgood > 0 {
|
||||
if tcorrupted > 0 || blockerr > 0 {
|
||||
// Rebuild the table.
|
||||
@@ -354,7 +372,7 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
mSeq = tSeq
|
||||
}
|
||||
// Add table to level 0.
|
||||
rec.addTable(0, file.Num(), uint64(size), min, max)
|
||||
rec.addTable(0, file.Num(), uint64(size), imin, imax)
|
||||
s.logf("table@recovery recovered @%d N·%d C·%d B·%d S·%d Q·%d", file.Num(), tgood, tcorrupted, blockerr, size, tSeq)
|
||||
} else {
|
||||
s.logf("table@recovery unrecoverable @%d C·%d B·%d S·%d", file.Num(), tcorrupted, blockerr, size)
|
||||
@@ -365,42 +383,56 @@ func recoverTable(s *session, o *opt.Options) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recover all tables.
|
||||
if len(ff1) > 0 {
|
||||
s.logf("table@recovery F·%d", len(ff1))
|
||||
s.markFileNum(ff1[len(ff1)-1].Num())
|
||||
for _, file := range ff1 {
|
||||
if len(tableFiles) > 0 {
|
||||
s.logf("table@recovery F·%d", len(tableFiles))
|
||||
|
||||
// Mark file number as used.
|
||||
s.markFileNum(tableFiles[len(tableFiles)-1].Num())
|
||||
|
||||
for _, file := range tableFiles {
|
||||
if err := recoverTable(file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.logf("table@recovery recovered F·%d N·%d C·%d Q·%d", len(ff1), good, corrupted, mSeq)
|
||||
|
||||
s.logf("table@recovery recovered F·%d N·%d C·%d Q·%d", len(tableFiles), good, corrupted, mSeq)
|
||||
}
|
||||
|
||||
// Set sequence number.
|
||||
rec.setSeq(mSeq + 1)
|
||||
|
||||
// Create new manifest.
|
||||
if err := s.create(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit.
|
||||
return s.commit(rec)
|
||||
}
|
||||
|
||||
func (d *DB) recoverJournal() error {
|
||||
s := d.s
|
||||
icmp := s.cmp
|
||||
|
||||
ff0, err := s.getFiles(storage.TypeJournal)
|
||||
func (db *DB) recoverJournal() error {
|
||||
// Get all tables and sort it by file number.
|
||||
journalFiles_, err := db.s.getFiles(storage.TypeJournal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ff1 := files(ff0)
|
||||
ff1.sort()
|
||||
ff2 := make([]storage.File, 0, len(ff1))
|
||||
for _, file := range ff1 {
|
||||
if file.Num() >= s.stJournalNum || file.Num() == s.stPrevJournalNum {
|
||||
s.markFileNum(file.Num())
|
||||
ff2 = append(ff2, file)
|
||||
journalFiles := files(journalFiles_)
|
||||
journalFiles.sort()
|
||||
|
||||
// Discard older journal.
|
||||
prev := -1
|
||||
for i, file := range journalFiles {
|
||||
if file.Num() >= db.s.stJournalNum {
|
||||
if prev >= 0 {
|
||||
i--
|
||||
journalFiles[i] = journalFiles[prev]
|
||||
}
|
||||
journalFiles = journalFiles[i:]
|
||||
break
|
||||
} else if file.Num() == db.s.stPrevJournalNum {
|
||||
prev = i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,38 +440,43 @@ func (d *DB) recoverJournal() error {
|
||||
var of storage.File
|
||||
var mem *memdb.DB
|
||||
batch := new(Batch)
|
||||
cm := newCMem(s)
|
||||
cm := newCMem(db.s)
|
||||
buf := new(util.Buffer)
|
||||
// Options.
|
||||
strict := s.o.GetStrict(opt.StrictJournal)
|
||||
checksum := s.o.GetStrict(opt.StrictJournalChecksum)
|
||||
writeBuffer := s.o.GetWriteBuffer()
|
||||
strict := db.s.o.GetStrict(opt.StrictJournal)
|
||||
checksum := db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||
writeBuffer := db.s.o.GetWriteBuffer()
|
||||
recoverJournal := func(file storage.File) error {
|
||||
s.logf("journal@recovery recovering @%d", file.Num())
|
||||
db.logf("journal@recovery recovering @%d", file.Num())
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Create/reset journal reader instance.
|
||||
if jr == nil {
|
||||
jr = journal.NewReader(reader, dropper{s, file}, strict, checksum)
|
||||
jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum)
|
||||
} else {
|
||||
jr.Reset(reader, dropper{s, file}, strict, checksum)
|
||||
jr.Reset(reader, dropper{db.s, file}, strict, checksum)
|
||||
}
|
||||
|
||||
// Flush memdb and remove obsolete journal file.
|
||||
if of != nil {
|
||||
if mem.Len() > 0 {
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := cm.commit(file.Num(), d.seq); err != nil {
|
||||
if err := cm.commit(file.Num(), db.seq); err != nil {
|
||||
return err
|
||||
}
|
||||
cm.reset()
|
||||
of.Remove()
|
||||
of = nil
|
||||
}
|
||||
// Reset memdb.
|
||||
|
||||
// Replay journal to memdb.
|
||||
mem.Reset()
|
||||
for {
|
||||
r, err := jr.Next()
|
||||
@@ -449,12 +486,14 @@ func (d *DB) recoverJournal() error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := buf.ReadFrom(r); err != nil {
|
||||
if strict {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
continue
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := batch.decode(buf.Bytes()); err != nil {
|
||||
return err
|
||||
@@ -462,28 +501,37 @@ func (d *DB) recoverJournal() error {
|
||||
if err := batch.memReplay(mem); err != nil {
|
||||
return err
|
||||
}
|
||||
d.seq = batch.seq + uint64(batch.len())
|
||||
|
||||
// Save sequence number.
|
||||
db.seq = batch.seq + uint64(batch.len())
|
||||
|
||||
// Flush it if large enough.
|
||||
if mem.Size() >= writeBuffer {
|
||||
// Large enough, flush it.
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
// Reset memdb.
|
||||
mem.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
of = file
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recover all journals.
|
||||
if len(ff2) > 0 {
|
||||
s.logf("journal@recovery F·%d", len(ff2))
|
||||
mem = memdb.New(icmp, writeBuffer)
|
||||
for _, file := range ff2 {
|
||||
if len(journalFiles) > 0 {
|
||||
db.logf("journal@recovery F·%d", len(journalFiles))
|
||||
|
||||
// Mark file number as used.
|
||||
db.s.markFileNum(journalFiles[len(journalFiles)-1].Num())
|
||||
|
||||
mem = memdb.New(db.s.icmp, writeBuffer)
|
||||
for _, file := range journalFiles {
|
||||
if err := recoverJournal(file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the last journal.
|
||||
if mem.Len() > 0 {
|
||||
if err := cm.flush(mem, 0); err != nil {
|
||||
@@ -491,52 +539,60 @@ func (d *DB) recoverJournal() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new journal.
|
||||
if _, err := d.newMem(0); err != nil {
|
||||
if _, err := db.newMem(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit.
|
||||
if err := cm.commit(d.journalFile.Num(), d.seq); err != nil {
|
||||
if err := cm.commit(db.journalFile.Num(), db.seq); err != nil {
|
||||
// Close journal.
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Remove the last journal.
|
||||
|
||||
// Remove the last obsolete journal file.
|
||||
if of != nil {
|
||||
of.Remove()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
s := d.s
|
||||
|
||||
ucmp := s.cmp.cmp
|
||||
func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
ikey := newIKey(key, seq, tSeek)
|
||||
|
||||
em, fm := d.getMems()
|
||||
for _, m := range [...]*memdb.DB{em, fm} {
|
||||
em, fm := db.getMems()
|
||||
for _, m := range [...]*memDB{em, fm} {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
mk, mv, me := m.Find(ikey)
|
||||
defer m.decref()
|
||||
|
||||
mk, mv, me := m.mdb.Find(ikey)
|
||||
if me == nil {
|
||||
ukey, _, t, ok := parseIkey(mk)
|
||||
if ok && ucmp.Compare(ukey, key) == 0 {
|
||||
if ok && db.s.icmp.uCompare(ukey, key) == 0 {
|
||||
if t == tDel {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return mv, nil
|
||||
return append([]byte{}, mv...), nil
|
||||
}
|
||||
} else if me != ErrNotFound {
|
||||
return nil, me
|
||||
}
|
||||
}
|
||||
|
||||
v := s.version()
|
||||
v := db.s.version()
|
||||
value, cSched, err := v.get(ikey, ro)
|
||||
v.release()
|
||||
if cSched {
|
||||
// Wake compaction.
|
||||
d.wakeCompaction(0)
|
||||
// Trigger table compaction.
|
||||
db.compTrigger(db.tcompTriggerC)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -544,15 +600,16 @@ func (d *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err
|
||||
// Get gets the value for the given key. It returns ErrNotFound if the
|
||||
// DB does not contain the key.
|
||||
//
|
||||
// The caller should not modify the contents of the returned slice, but
|
||||
// it is safe to modify the contents of the argument after Get returns.
|
||||
func (d *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
err = d.ok()
|
||||
// The returned slice is its own copy, it is safe to modify the contents
|
||||
// of the returned slice.
|
||||
// It is safe to modify the contents of the argument after Get returns.
|
||||
func (db *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
err = db.ok()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return d.get(key, d.getSeq(), ro)
|
||||
return db.get(key, db.getSeq(), ro)
|
||||
}
|
||||
|
||||
// NewIterator returns an iterator for the latest snapshot of the
|
||||
@@ -571,14 +628,14 @@ func (d *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
// The iterator must be released after use, by calling Release method.
|
||||
//
|
||||
// Also read Iterator documentation of the leveldb/iterator package.
|
||||
func (d *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
if err := d.ok(); err != nil {
|
||||
func (db *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
if err := db.ok(); err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
|
||||
p := d.newSnapshot()
|
||||
defer p.Release()
|
||||
return p.NewIterator(slice, ro)
|
||||
snap := db.newSnapshot()
|
||||
defer snap.Release()
|
||||
return snap.NewIterator(slice, ro)
|
||||
}
|
||||
|
||||
// GetSnapshot returns a latest snapshot of the underlying DB. A snapshot
|
||||
@@ -586,12 +643,12 @@ func (d *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterat
|
||||
// content of snapshot are guaranteed to be consistent.
|
||||
//
|
||||
// The snapshot must be released after use, by calling Release method.
|
||||
func (d *DB) GetSnapshot() (*Snapshot, error) {
|
||||
if err := d.ok(); err != nil {
|
||||
func (db *DB) GetSnapshot() (*Snapshot, error) {
|
||||
if err := db.ok(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.newSnapshot(), nil
|
||||
return db.newSnapshot(), nil
|
||||
}
|
||||
|
||||
// GetProperty returns value of the given property name.
|
||||
@@ -603,8 +660,18 @@ func (d *DB) GetSnapshot() (*Snapshot, error) {
|
||||
// Returns statistics of the underlying DB.
|
||||
// leveldb.sstables
|
||||
// Returns sstables list for each level.
|
||||
func (d *DB) GetProperty(name string) (value string, err error) {
|
||||
err = d.ok()
|
||||
// leveldb.blockpool
|
||||
// Returns block pool stats.
|
||||
// leveldb.cachedblock
|
||||
// Returns size of cached block.
|
||||
// leveldb.openedtables
|
||||
// Returns number of opened tables.
|
||||
// leveldb.alivesnaps
|
||||
// Returns number of alive snapshots.
|
||||
// leveldb.aliveiters
|
||||
// Returns number of alive iterators.
|
||||
func (db *DB) GetProperty(name string) (value string, err error) {
|
||||
err = db.ok()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -613,11 +680,9 @@ func (d *DB) GetProperty(name string) (value string, err error) {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
return "", errors.New("leveldb: GetProperty: unknown property: " + name)
|
||||
}
|
||||
|
||||
p := name[len(prefix):]
|
||||
|
||||
s := d.s
|
||||
v := s.version()
|
||||
v := db.s.version()
|
||||
defer v.release()
|
||||
|
||||
switch {
|
||||
@@ -634,22 +699,36 @@ func (d *DB) GetProperty(name string) (value string, err error) {
|
||||
value = "Compactions\n" +
|
||||
" Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)\n" +
|
||||
"-------+------------+---------------+---------------+---------------+---------------\n"
|
||||
for level, tt := range v.tables {
|
||||
duration, read, write := d.compStats[level].get()
|
||||
if len(tt) == 0 && duration == 0 {
|
||||
for level, tables := range v.tables {
|
||||
duration, read, write := db.compStats[level].get()
|
||||
if len(tables) == 0 && duration == 0 {
|
||||
continue
|
||||
}
|
||||
value += fmt.Sprintf(" %3d | %10d | %13.5f | %13.5f | %13.5f | %13.5f\n",
|
||||
level, len(tt), float64(tt.size())/1048576.0, duration.Seconds(),
|
||||
level, len(tables), float64(tables.size())/1048576.0, duration.Seconds(),
|
||||
float64(read)/1048576.0, float64(write)/1048576.0)
|
||||
}
|
||||
case p == "sstables":
|
||||
for level, tt := range v.tables {
|
||||
for level, tables := range v.tables {
|
||||
value += fmt.Sprintf("--- level %d ---\n", level)
|
||||
for _, t := range tt {
|
||||
value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.file.Num(), t.size, t.min, t.max)
|
||||
for _, t := range tables {
|
||||
value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.file.Num(), t.size, t.imin, t.imax)
|
||||
}
|
||||
}
|
||||
case p == "blockpool":
|
||||
value = fmt.Sprintf("%v", db.s.tops.bpool)
|
||||
case p == "cachedblock":
|
||||
if bc := db.s.o.GetBlockCache(); bc != nil {
|
||||
value = fmt.Sprintf("%d", bc.Size())
|
||||
} else {
|
||||
value = "<nil>"
|
||||
}
|
||||
case p == "openedtables":
|
||||
value = fmt.Sprintf("%d", db.s.tops.cache.Size())
|
||||
case p == "alivesnaps":
|
||||
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveSnaps))
|
||||
case p == "aliveiters":
|
||||
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
|
||||
default:
|
||||
err = errors.New("leveldb: GetProperty: unknown property: " + name)
|
||||
}
|
||||
@@ -657,29 +736,29 @@ func (d *DB) GetProperty(name string) (value string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetApproximateSizes calculates approximate sizes of the given key ranges.
|
||||
// SizeOf calculates approximate sizes of the given key ranges.
|
||||
// The length of the returned sizes are equal with the length of the given
|
||||
// ranges. The returned sizes measure storage space usage, so if the user
|
||||
// data compresses by a factor of ten, the returned sizes will be one-tenth
|
||||
// the size of the corresponding user data size.
|
||||
// The results may not include the sizes of recently written data.
|
||||
func (d *DB) GetApproximateSizes(ranges []util.Range) (Sizes, error) {
|
||||
if err := d.ok(); err != nil {
|
||||
func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) {
|
||||
if err := db.ok(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := d.s.version()
|
||||
v := db.s.version()
|
||||
defer v.release()
|
||||
|
||||
sizes := make(Sizes, 0, len(ranges))
|
||||
for _, r := range ranges {
|
||||
min := newIKey(r.Start, kMaxSeq, tSeek)
|
||||
max := newIKey(r.Limit, kMaxSeq, tSeek)
|
||||
start, err := v.getApproximateOffset(min)
|
||||
imin := newIKey(r.Start, kMaxSeq, tSeek)
|
||||
imax := newIKey(r.Limit, kMaxSeq, tSeek)
|
||||
start, err := v.offsetOf(imin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limit, err := v.getApproximateOffset(max)
|
||||
limit, err := v.offsetOf(imax)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -693,105 +772,63 @@ func (d *DB) GetApproximateSizes(ranges []util.Range) (Sizes, error) {
|
||||
return sizes, nil
|
||||
}
|
||||
|
||||
// CompactRange compacts the underlying DB for the given key range.
|
||||
// In particular, deleted and overwritten versions are discarded,
|
||||
// and the data is rearranged to reduce the cost of operations
|
||||
// needed to access the data. This operation should typically only
|
||||
// be invoked by users who understand the underlying implementation.
|
||||
//
|
||||
// A nil Range.Start is treated as a key before all keys in the DB.
|
||||
// And a nil Range.Limit is treated as a key after all keys in the DB.
|
||||
// Therefore if both is nil then it will compact entire DB.
|
||||
func (d *DB) CompactRange(r util.Range) error {
|
||||
err := d.ok()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cch := make(chan struct{})
|
||||
req := &cReq{
|
||||
level: -1,
|
||||
min: r.Start,
|
||||
max: r.Limit,
|
||||
cch: cch,
|
||||
}
|
||||
|
||||
// Push manual compaction request.
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case err := <-d.compErrCh:
|
||||
return err
|
||||
case d.compReqCh <- req:
|
||||
}
|
||||
// Wait for compaction
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case <-cch:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the DB. This will also releases any outstanding snapshot.
|
||||
// Close closes the DB. This will also releases any outstanding snapshot and
|
||||
// abort any in-flight compaction.
|
||||
//
|
||||
// It is not safe to close a DB until all outstanding iterators are released.
|
||||
// It is valid to call Close multiple times. Other methods should not be
|
||||
// called after the DB has been closed.
|
||||
func (d *DB) Close() error {
|
||||
if !d.setClosed() {
|
||||
func (db *DB) Close() error {
|
||||
if !db.setClosed() {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
s := d.s
|
||||
start := time.Now()
|
||||
s.log("db@close closing")
|
||||
db.log("db@close closing")
|
||||
|
||||
// Clear the finalizer.
|
||||
runtime.SetFinalizer(d, nil)
|
||||
runtime.SetFinalizer(db, nil)
|
||||
|
||||
// Get compaction error.
|
||||
var err error
|
||||
select {
|
||||
case err = <-d.compErrCh:
|
||||
case err = <-db.compErrC:
|
||||
default:
|
||||
}
|
||||
|
||||
close(d.closeCh)
|
||||
close(db.closeC)
|
||||
|
||||
// wait for the WaitGroup
|
||||
d.closeWg.Wait()
|
||||
// Wait for the close WaitGroup.
|
||||
db.closeW.Wait()
|
||||
|
||||
// close journal
|
||||
if d.journal != nil {
|
||||
d.journal.Close()
|
||||
d.journalWriter.Close()
|
||||
// Close journal.
|
||||
db.writeLockC <- struct{}{}
|
||||
if db.journal != nil {
|
||||
db.journal.Close()
|
||||
db.journalWriter.Close()
|
||||
}
|
||||
|
||||
// close session
|
||||
s.close()
|
||||
s.logf("db@close done T·%v", time.Since(start))
|
||||
s.release()
|
||||
// Close session.
|
||||
db.s.close()
|
||||
db.logf("db@close done T·%v", time.Since(start))
|
||||
db.s.release()
|
||||
|
||||
if d.closer != nil {
|
||||
if err1 := d.closer.Close(); err == nil {
|
||||
if db.closer != nil {
|
||||
if err1 := db.closer.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
d.s = nil
|
||||
d.mem = nil
|
||||
d.frozenMem = nil
|
||||
d.journal = nil
|
||||
d.journalWriter = nil
|
||||
d.journalFile = nil
|
||||
d.frozenJournalFile = nil
|
||||
d.snapsRoot = snapshotElement{}
|
||||
d.closer = nil
|
||||
|
||||
close(d.writeCh)
|
||||
close(d.journalCh)
|
||||
close(d.compReqCh)
|
||||
// NIL'ing pointers.
|
||||
db.s = nil
|
||||
db.mem = nil
|
||||
db.frozenMem = nil
|
||||
db.journal = nil
|
||||
db.journalWriter = nil
|
||||
db.journalFile = nil
|
||||
db.frozenJournalFile = nil
|
||||
db.snapsRoot = snapshotElement{}
|
||||
db.closer = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+335
-179
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errTransactExiting = errors.New("leveldb: transact exiting")
|
||||
errCompactionTransactExiting = errors.New("leveldb: compaction transact exiting")
|
||||
)
|
||||
|
||||
type cStats struct {
|
||||
@@ -61,12 +61,6 @@ func (p *cStatsStaging) stopTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
type cReq struct {
|
||||
level int
|
||||
min, max iKey
|
||||
cch chan<- struct{}
|
||||
}
|
||||
|
||||
type cMem struct {
|
||||
s *session
|
||||
level int
|
||||
@@ -80,7 +74,7 @@ func newCMem(s *session) *cMem {
|
||||
func (c *cMem) flush(mem *memdb.DB, level int) error {
|
||||
s := c.s
|
||||
|
||||
// Write memdb to table
|
||||
// Write memdb to table.
|
||||
iter := mem.NewIterator(nil)
|
||||
defer iter.Release()
|
||||
t, n, err := s.tops.createFrom(iter)
|
||||
@@ -88,12 +82,13 @@ func (c *cMem) flush(mem *memdb.DB, level int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pick level.
|
||||
if level < 0 {
|
||||
level = s.version_NB().pickLevel(t.min.ukey(), t.max.ukey())
|
||||
level = s.version_NB().pickLevel(t.imin.ukey(), t.imax.ukey())
|
||||
}
|
||||
c.rec.addTableFile(level, t)
|
||||
|
||||
s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.min, t.max)
|
||||
s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
|
||||
|
||||
c.level = level
|
||||
return nil
|
||||
@@ -106,95 +101,152 @@ func (c *cMem) reset() {
|
||||
func (c *cMem) commit(journal, seq uint64) error {
|
||||
c.rec.setJournalNum(journal)
|
||||
c.rec.setSeq(seq)
|
||||
// Commit changes
|
||||
|
||||
// Commit changes.
|
||||
return c.s.commit(c.rec)
|
||||
}
|
||||
|
||||
func (d *DB) compactionError() {
|
||||
func (db *DB) compactionError() {
|
||||
var err error
|
||||
noerr:
|
||||
for {
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return
|
||||
case err = <-d.compErrSetCh:
|
||||
case err = <-db.compErrSetC:
|
||||
if err != nil {
|
||||
goto haserr
|
||||
}
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
}
|
||||
haserr:
|
||||
for {
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return
|
||||
case err = <-d.compErrSetCh:
|
||||
case db.compErrC <- err:
|
||||
case err = <-db.compErrSetC:
|
||||
if err == nil {
|
||||
goto noerr
|
||||
}
|
||||
case d.compErrCh <- err:
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) transact(name string, exec, rollback func() error) {
|
||||
s := d.s
|
||||
type compactionTransactCounter int
|
||||
|
||||
func (cnt *compactionTransactCounter) incr() {
|
||||
*cnt++
|
||||
}
|
||||
|
||||
func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactCounter) error, rollback func() error) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
if x == errTransactExiting && rollback != nil {
|
||||
if x == errCompactionTransactExiting && rollback != nil {
|
||||
if err := rollback(); err != nil {
|
||||
s.logf("%s rollback error %q", name, err)
|
||||
db.logf("%s rollback error %q", name, err)
|
||||
}
|
||||
}
|
||||
panic(x)
|
||||
}
|
||||
}()
|
||||
|
||||
const (
|
||||
backoffMin = 1 * time.Second
|
||||
backoffMax = 8 * time.Second
|
||||
backoffMul = 2 * time.Second
|
||||
)
|
||||
backoff := backoffMin
|
||||
backoffT := time.NewTimer(backoff)
|
||||
lastCnt := compactionTransactCounter(0)
|
||||
for n := 0; ; n++ {
|
||||
if d.isClosed() {
|
||||
s.logf("%s exiting", name)
|
||||
panic(errTransactExiting)
|
||||
// Check wether the DB is closed.
|
||||
if db.isClosed() {
|
||||
db.logf("%s exiting", name)
|
||||
db.compactionExitTransact()
|
||||
} else if n > 0 {
|
||||
s.logf("%s retrying N·%d", name, n)
|
||||
db.logf("%s retrying N·%d", name, n)
|
||||
}
|
||||
err := exec()
|
||||
|
||||
// Execute.
|
||||
cnt := compactionTransactCounter(0)
|
||||
err := exec(&cnt)
|
||||
|
||||
// Set compaction error status.
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
s.logf("%s exiting", name)
|
||||
panic(errTransactExiting)
|
||||
case d.compErrSetCh <- err:
|
||||
case db.compErrSetC <- err:
|
||||
case _, _ = <-db.closeC:
|
||||
db.logf("%s exiting", name)
|
||||
db.compactionExitTransact()
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
s.logf("%s error %q", name, err)
|
||||
time.Sleep(time.Second)
|
||||
db.logf("%s error I·%d %q", name, cnt, err)
|
||||
|
||||
// Reset backoff duration if counter is advancing.
|
||||
if cnt > lastCnt {
|
||||
backoff = backoffMin
|
||||
lastCnt = cnt
|
||||
}
|
||||
|
||||
// Backoff.
|
||||
backoffT.Reset(backoff)
|
||||
if backoff < backoffMax {
|
||||
backoff *= backoffMul
|
||||
if backoff > backoffMax {
|
||||
backoff = backoffMax
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-backoffT.C:
|
||||
case _, _ = <-db.closeC:
|
||||
db.logf("%s exiting", name)
|
||||
db.compactionExitTransact()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) memCompaction() {
|
||||
s := d.s
|
||||
c := newCMem(s)
|
||||
stats := new(cStatsStaging)
|
||||
mem := d.getFrozenMem()
|
||||
func (db *DB) compactionExitTransact() {
|
||||
panic(errCompactionTransactExiting)
|
||||
}
|
||||
|
||||
s.logf("mem@flush N·%d S·%s", mem.Len(), shortenb(mem.Size()))
|
||||
func (db *DB) memCompaction() {
|
||||
mem := db.getFrozenMem()
|
||||
if mem == nil {
|
||||
return
|
||||
}
|
||||
defer mem.decref()
|
||||
|
||||
c := newCMem(db.s)
|
||||
stats := new(cStatsStaging)
|
||||
|
||||
db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size()))
|
||||
|
||||
// Don't compact empty memdb.
|
||||
if mem.Len() == 0 {
|
||||
s.logf("mem@flush skipping")
|
||||
if mem.mdb.Len() == 0 {
|
||||
db.logf("mem@flush skipping")
|
||||
// drop frozen mem
|
||||
d.dropFrozenMem()
|
||||
db.dropFrozenMem()
|
||||
return
|
||||
}
|
||||
|
||||
d.transact("mem@flush", func() (err error) {
|
||||
// Pause table compaction.
|
||||
ch := make(chan struct{})
|
||||
select {
|
||||
case db.tcompPauseC <- (chan<- struct{})(ch):
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
|
||||
db.compactionTransact("mem@flush", func(cnt *compactionTransactCounter) (err error) {
|
||||
stats.startTimer()
|
||||
defer stats.stopTimer()
|
||||
return c.flush(mem, -1)
|
||||
return c.flush(mem.mdb, -1)
|
||||
}, func() error {
|
||||
for _, r := range c.rec.addedTables {
|
||||
s.logf("mem@flush rollback @%d", r.num)
|
||||
f := s.getTableFile(r.num)
|
||||
db.logf("mem@flush rollback @%d", r.num)
|
||||
f := db.s.getTableFile(r.num)
|
||||
if err := f.Remove(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,54 +254,59 @@ func (d *DB) memCompaction() {
|
||||
return nil
|
||||
})
|
||||
|
||||
d.transact("mem@commit", func() (err error) {
|
||||
db.compactionTransact("mem@commit", func(cnt *compactionTransactCounter) (err error) {
|
||||
stats.startTimer()
|
||||
defer stats.stopTimer()
|
||||
return c.commit(d.journalFile.Num(), d.frozenSeq)
|
||||
return c.commit(db.journalFile.Num(), db.frozenSeq)
|
||||
}, nil)
|
||||
|
||||
s.logf("mem@flush commited F·%d T·%v", len(c.rec.addedTables), stats.duration)
|
||||
db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration)
|
||||
|
||||
for _, r := range c.rec.addedTables {
|
||||
stats.write += r.size
|
||||
}
|
||||
d.compStats[c.level].add(stats)
|
||||
db.compStats[c.level].add(stats)
|
||||
|
||||
// drop frozen mem
|
||||
d.dropFrozenMem()
|
||||
// Drop frozen mem.
|
||||
db.dropFrozenMem()
|
||||
|
||||
c = nil
|
||||
// Resume table compaction.
|
||||
select {
|
||||
case <-ch:
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger table compaction.
|
||||
db.compTrigger(db.mcompTriggerC)
|
||||
}
|
||||
|
||||
func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
s := d.s
|
||||
ucmp := s.cmp.cmp
|
||||
|
||||
func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
|
||||
rec := new(sessionRecord)
|
||||
rec.addCompactionPointer(c.level, c.max)
|
||||
rec.addCompactionPointer(c.level, c.imax)
|
||||
|
||||
if !noTrivial && c.trivial() {
|
||||
t := c.tables[0][0]
|
||||
s.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1)
|
||||
db.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1)
|
||||
rec.deleteTable(c.level, t.file.Num())
|
||||
rec.addTableFile(c.level+1, t)
|
||||
d.transact("table@move", func() (err error) {
|
||||
return s.commit(rec)
|
||||
db.compactionTransact("table@move", func(cnt *compactionTransactCounter) (err error) {
|
||||
return db.s.commit(rec)
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var stats [2]cStatsStaging
|
||||
for i, tt := range c.tables {
|
||||
for _, t := range tt {
|
||||
for i, tables := range c.tables {
|
||||
for _, t := range tables {
|
||||
stats[i].read += t.size
|
||||
// Insert deleted tables into record
|
||||
rec.deleteTable(c.level+i, t.file.Num())
|
||||
}
|
||||
}
|
||||
sourceSize := int(stats[0].read + stats[1].read)
|
||||
minSeq := d.minSeq()
|
||||
s.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq)
|
||||
minSeq := db.minSeq()
|
||||
db.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq)
|
||||
|
||||
var snapUkey []byte
|
||||
var snapHasUkey bool
|
||||
@@ -257,7 +314,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
var snapIter int
|
||||
var snapDropCnt int
|
||||
var dropCnt int
|
||||
d.transact("table@build", func() (err error) {
|
||||
db.compactionTransact("table@build", func(cnt *compactionTransactCounter) (err error) {
|
||||
ukey := append([]byte{}, snapUkey...)
|
||||
hasUkey := snapHasUkey
|
||||
lseq := snapSeq
|
||||
@@ -272,7 +329,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
}
|
||||
rec.addTableFile(c.level+1, t)
|
||||
stats[1].write += t.size
|
||||
s.logf("table@build created L%d@%d N·%d S·%s %q:%q", c.level+1, t.file.Num(), tw.tw.EntriesLen(), shortenb(int(t.size)), t.min, t.max)
|
||||
db.logf("table@build created L%d@%d N·%d S·%s %q:%q", c.level+1, t.file.Num(), tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -288,30 +345,17 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
iter := c.newIterator()
|
||||
defer iter.Release()
|
||||
for i := 0; iter.Next(); i++ {
|
||||
// Incr transact counter.
|
||||
cnt.incr()
|
||||
|
||||
// Skip until last state.
|
||||
if i < snapIter {
|
||||
continue
|
||||
}
|
||||
|
||||
// Prioritize memdb compaction.
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
err = ErrClosed
|
||||
return
|
||||
case cch := <-d.compMemCh:
|
||||
stats[1].stopTimer()
|
||||
d.memCompaction()
|
||||
d.compMemAckCh <- struct{}{}
|
||||
if cch != nil {
|
||||
cch <- struct{}{}
|
||||
}
|
||||
stats[1].startTimer()
|
||||
default:
|
||||
}
|
||||
ikey := iKey(iter.Key())
|
||||
|
||||
key := iKey(iter.Key())
|
||||
|
||||
if c.shouldStopBefore(key) && tw != nil {
|
||||
if c.shouldStopBefore(ikey) && tw != nil {
|
||||
err = finish()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -331,15 +375,15 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
snapSched = false
|
||||
}
|
||||
|
||||
if seq, t, ok := key.parseNum(); !ok {
|
||||
if seq, vt, ok := ikey.parseNum(); !ok {
|
||||
// Don't drop error keys
|
||||
ukey = ukey[:0]
|
||||
hasUkey = false
|
||||
lseq = kMaxSeq
|
||||
} else {
|
||||
if !hasUkey || ucmp.Compare(key.ukey(), ukey) != 0 {
|
||||
if !hasUkey || db.s.icmp.uCompare(ikey.ukey(), ukey) != 0 {
|
||||
// First occurrence of this user key
|
||||
ukey = append(ukey[:0], key.ukey()...)
|
||||
ukey = append(ukey[:0], ikey.ukey()...)
|
||||
hasUkey = true
|
||||
lseq = kMaxSeq
|
||||
}
|
||||
@@ -348,7 +392,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
if lseq <= minSeq {
|
||||
// Dropped because newer entry for same user key exist
|
||||
drop = true // (A)
|
||||
} else if t == tDel && seq <= minSeq && c.isBaseLevelForKey(ukey) {
|
||||
} else if vt == tDel && seq <= minSeq && c.baseLevelForKey(ukey) {
|
||||
// For this user key:
|
||||
// (1) there is no data in higher levels
|
||||
// (2) data in lower levels will have larger seq numbers
|
||||
@@ -368,14 +412,24 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
|
||||
// Create new table if not already
|
||||
if tw == nil {
|
||||
tw, err = s.tops.create()
|
||||
// Check for pause event.
|
||||
select {
|
||||
case ch := <-db.tcompPauseC:
|
||||
db.pauseCompaction(ch)
|
||||
case _, _ = <-db.closeC:
|
||||
db.compactionExitTransact()
|
||||
default:
|
||||
}
|
||||
|
||||
// Create new table.
|
||||
tw, err = db.s.tops.create()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write key/value into table
|
||||
err = tw.add(key, iter.Value())
|
||||
err = tw.append(ikey, iter.Value())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -407,8 +461,8 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
return
|
||||
}, func() error {
|
||||
for _, r := range rec.addedTables {
|
||||
s.logf("table@build rollback @%d", r.num)
|
||||
f := s.getTableFile(r.num)
|
||||
db.logf("table@build rollback @%d", r.num)
|
||||
f := db.s.getTableFile(r.num)
|
||||
if err := f.Remove(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -417,117 +471,219 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) {
|
||||
})
|
||||
|
||||
// Commit changes
|
||||
d.transact("table@commit", func() (err error) {
|
||||
db.compactionTransact("table@commit", func(cnt *compactionTransactCounter) (err error) {
|
||||
stats[1].startTimer()
|
||||
defer stats[1].stopTimer()
|
||||
return s.commit(rec)
|
||||
return db.s.commit(rec)
|
||||
}, nil)
|
||||
|
||||
resultSize := int(int(stats[1].write))
|
||||
s.logf("table@compaction commited F%s S%s D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), dropCnt, stats[1].duration)
|
||||
resultSize := int(stats[1].write)
|
||||
db.logf("table@compaction committed F%s S%s D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), dropCnt, stats[1].duration)
|
||||
|
||||
// Save compaction stats
|
||||
for i := range stats {
|
||||
d.compStats[c.level+1].add(&stats[i])
|
||||
db.compStats[c.level+1].add(&stats[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) compaction() {
|
||||
s := d.s
|
||||
func (db *DB) tableRangeCompaction(level int, umin, umax []byte) {
|
||||
db.logf("table@compaction range L%d %q:%q", level, umin, umax)
|
||||
|
||||
if level >= 0 {
|
||||
if c := db.s.getCompactionRange(level, umin, umax); c != nil {
|
||||
db.tableCompaction(c, true)
|
||||
}
|
||||
} else {
|
||||
v := db.s.version_NB()
|
||||
|
||||
m := 1
|
||||
for i, t := range v.tables[1:] {
|
||||
if t.overlaps(db.s.icmp, umin, umax, false) {
|
||||
m = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
for level := 0; level < m; level++ {
|
||||
if c := db.s.getCompactionRange(level, umin, umax); c != nil {
|
||||
db.tableCompaction(c, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) tableAutoCompaction() {
|
||||
if c := db.s.pickCompaction(); c != nil {
|
||||
db.tableCompaction(c, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) tableNeedCompaction() bool {
|
||||
return db.s.version_NB().needCompaction()
|
||||
}
|
||||
|
||||
func (db *DB) pauseCompaction(ch chan<- struct{}) {
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
case _, _ = <-db.closeC:
|
||||
db.compactionExitTransact()
|
||||
}
|
||||
}
|
||||
|
||||
type cCmd interface {
|
||||
ack(err error)
|
||||
}
|
||||
|
||||
type cIdle struct {
|
||||
ackC chan<- error
|
||||
}
|
||||
|
||||
func (r cIdle) ack(err error) {
|
||||
r.ackC <- err
|
||||
}
|
||||
|
||||
type cRange struct {
|
||||
level int
|
||||
min, max []byte
|
||||
ackC chan<- error
|
||||
}
|
||||
|
||||
func (r cRange) ack(err error) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
if r.ackC != nil {
|
||||
r.ackC <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) compSendIdle(compC chan<- cCmd) error {
|
||||
ch := make(chan error)
|
||||
defer close(ch)
|
||||
// Send cmd.
|
||||
select {
|
||||
case compC <- cIdle{ch}:
|
||||
case err := <-db.compErrC:
|
||||
return err
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
// Wait cmd.
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (db *DB) compSendRange(compC chan<- cCmd, level int, min, max []byte) (err error) {
|
||||
ch := make(chan error)
|
||||
defer close(ch)
|
||||
// Send cmd.
|
||||
select {
|
||||
case compC <- cRange{level, min, max, ch}:
|
||||
case err := <-db.compErrC:
|
||||
return err
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
// Wait cmd.
|
||||
select {
|
||||
case err = <-db.compErrC:
|
||||
case err = <-ch:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) compTrigger(compTriggerC chan struct{}) {
|
||||
select {
|
||||
case compTriggerC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) mCompaction() {
|
||||
var x cCmd
|
||||
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
if x != errTransactExiting {
|
||||
if x != errCompactionTransactExiting {
|
||||
panic(x)
|
||||
}
|
||||
}
|
||||
d.closeWg.Done()
|
||||
if x != nil {
|
||||
x.ack(ErrClosed)
|
||||
}
|
||||
db.closeW.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
var cch chan<- struct{}
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
case x = <-db.mcompCmdC:
|
||||
db.memCompaction()
|
||||
x.ack(nil)
|
||||
x = nil
|
||||
case <-db.mcompTriggerC:
|
||||
db.memCompaction()
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
case cch = <-d.compMemCh:
|
||||
d.memCompaction()
|
||||
d.compMemAckCh <- struct{}{}
|
||||
case cch = <-d.compCh:
|
||||
case creq := <-d.compReqCh:
|
||||
if creq == nil {
|
||||
continue
|
||||
}
|
||||
s.logf("range compaction L%d %v:%v", creq.level, creq.min, creq.max)
|
||||
if creq.level >= 0 {
|
||||
c := s.getCompactionRange(creq.level, creq.min, creq.max)
|
||||
if c != nil {
|
||||
d.doCompaction(c, true)
|
||||
}
|
||||
} else {
|
||||
v := s.version_NB()
|
||||
maxLevel := 1
|
||||
for i, tt := range v.tables[1:] {
|
||||
if tt.isOverlaps(creq.min, creq.max, true, s.cmp) {
|
||||
maxLevel = i + 1
|
||||
}
|
||||
}
|
||||
for i := 0; i < maxLevel; i++ {
|
||||
c := s.getCompactionRange(i, creq.min, creq.max)
|
||||
if c != nil {
|
||||
d.doCompaction(c, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
cch = creq.cch
|
||||
}
|
||||
if s.version_NB().needCompaction() {
|
||||
d.doCompaction(s.pickCompaction(), false)
|
||||
select {
|
||||
case d.compCh <- nil:
|
||||
default:
|
||||
}
|
||||
}
|
||||
if cch != nil {
|
||||
func() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
cch <- struct{}{}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) wakeCompaction(wait int) error {
|
||||
switch wait {
|
||||
case 0:
|
||||
select {
|
||||
case d.compCh <- nil:
|
||||
default:
|
||||
func (db *DB) tCompaction() {
|
||||
var x cCmd
|
||||
var ackQ []cCmd
|
||||
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
if x != errCompactionTransactExiting {
|
||||
panic(x)
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case err := <-d.compErrCh:
|
||||
return err
|
||||
case d.compCh <- nil:
|
||||
for i := range ackQ {
|
||||
ackQ[i].ack(ErrClosed)
|
||||
ackQ[i] = nil
|
||||
}
|
||||
case 2:
|
||||
cch := make(chan struct{})
|
||||
defer close(cch)
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case err := <-d.compErrCh:
|
||||
return err
|
||||
case d.compCh <- (chan<- struct{})(cch):
|
||||
if x != nil {
|
||||
x.ack(ErrClosed)
|
||||
}
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case err := <-d.compErrCh:
|
||||
return err
|
||||
case <-cch:
|
||||
db.closeW.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
if db.tableNeedCompaction() {
|
||||
select {
|
||||
case x = <-db.tcompCmdC:
|
||||
case <-db.tcompTriggerC:
|
||||
case ch := <-db.tcompPauseC:
|
||||
db.pauseCompaction(ch)
|
||||
continue
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
for i := range ackQ {
|
||||
ackQ[i].ack(nil)
|
||||
ackQ[i] = nil
|
||||
}
|
||||
ackQ = ackQ[:0]
|
||||
select {
|
||||
case x = <-db.tcompCmdC:
|
||||
case <-db.tcompTriggerC:
|
||||
case ch := <-db.tcompPauseC:
|
||||
db.pauseCompaction(ch)
|
||||
continue
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
}
|
||||
if x != nil {
|
||||
switch cmd := x.(type) {
|
||||
case cIdle:
|
||||
ackQ = append(ackQ, x)
|
||||
case cRange:
|
||||
db.tableRangeCompaction(cmd.level, cmd.min, cmd.max)
|
||||
x.ack(nil)
|
||||
}
|
||||
x = nil
|
||||
}
|
||||
db.tableAutoCompaction()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+44
-20
@@ -9,8 +9,9 @@ package leveldb
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
@@ -20,46 +21,61 @@ var (
|
||||
errInvalidIkey = errors.New("leveldb: Iterator: invalid internal key")
|
||||
)
|
||||
|
||||
func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
s := db.s
|
||||
type memdbReleaser struct {
|
||||
once sync.Once
|
||||
m *memDB
|
||||
}
|
||||
|
||||
func (mr *memdbReleaser) Release() {
|
||||
mr.once.Do(func() {
|
||||
mr.m.decref()
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
em, fm := db.getMems()
|
||||
v := s.version()
|
||||
v := db.s.version()
|
||||
|
||||
ti := v.getIterators(slice, ro)
|
||||
n := len(ti) + 2
|
||||
i := make([]iterator.Iterator, 0, n)
|
||||
i = append(i, em.NewIterator(slice))
|
||||
emi := em.mdb.NewIterator(slice)
|
||||
emi.SetReleaser(&memdbReleaser{m: em})
|
||||
i = append(i, emi)
|
||||
if fm != nil {
|
||||
i = append(i, fm.NewIterator(slice))
|
||||
fmi := fm.mdb.NewIterator(slice)
|
||||
fmi.SetReleaser(&memdbReleaser{m: fm})
|
||||
i = append(i, fmi)
|
||||
}
|
||||
i = append(i, ti...)
|
||||
strict := s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator)
|
||||
mi := iterator.NewMergedIterator(i, s.cmp, strict)
|
||||
strict := db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator)
|
||||
mi := iterator.NewMergedIterator(i, db.s.icmp, strict)
|
||||
mi.SetReleaser(&versionReleaser{v: v})
|
||||
return mi
|
||||
}
|
||||
|
||||
func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *dbIter {
|
||||
var slice_ *util.Range
|
||||
var islice *util.Range
|
||||
if slice != nil {
|
||||
slice_ = &util.Range{}
|
||||
islice = &util.Range{}
|
||||
if slice.Start != nil {
|
||||
slice_.Start = newIKey(slice.Start, kMaxSeq, tSeek)
|
||||
islice.Start = newIKey(slice.Start, kMaxSeq, tSeek)
|
||||
}
|
||||
if slice.Limit != nil {
|
||||
slice_.Limit = newIKey(slice.Limit, kMaxSeq, tSeek)
|
||||
islice.Limit = newIKey(slice.Limit, kMaxSeq, tSeek)
|
||||
}
|
||||
}
|
||||
rawIter := db.newRawIterator(slice_, ro)
|
||||
rawIter := db.newRawIterator(islice, ro)
|
||||
iter := &dbIter{
|
||||
cmp: db.s.cmp.cmp,
|
||||
db: db,
|
||||
icmp: db.s.icmp,
|
||||
iter: rawIter,
|
||||
seq: seq,
|
||||
strict: db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator),
|
||||
key: make([]byte, 0),
|
||||
value: make([]byte, 0),
|
||||
}
|
||||
atomic.AddInt32(&db.aliveIters, 1)
|
||||
runtime.SetFinalizer(iter, (*dbIter).Release)
|
||||
return iter
|
||||
}
|
||||
@@ -76,7 +92,8 @@ const (
|
||||
|
||||
// dbIter represent an interator states over a database session.
|
||||
type dbIter struct {
|
||||
cmp comparer.BasicComparer
|
||||
db *DB
|
||||
icmp *iComparer
|
||||
iter iterator.Iterator
|
||||
seq uint64
|
||||
strict bool
|
||||
@@ -166,7 +183,7 @@ func (i *dbIter) next() bool {
|
||||
i.key = append(i.key[:0], ukey...)
|
||||
i.dir = dirForward
|
||||
case tVal:
|
||||
if i.dir == dirSOI || i.cmp.Compare(ukey, i.key) > 0 {
|
||||
if i.dir == dirSOI || i.icmp.uCompare(ukey, i.key) > 0 {
|
||||
i.key = append(i.key[:0], ukey...)
|
||||
i.value = append(i.value[:0], i.iter.Value()...)
|
||||
i.dir = dirForward
|
||||
@@ -211,7 +228,7 @@ func (i *dbIter) prev() bool {
|
||||
ukey, seq, t, ok := parseIkey(i.iter.Key())
|
||||
if ok {
|
||||
if seq <= i.seq {
|
||||
if !del && i.cmp.Compare(ukey, i.key) < 0 {
|
||||
if !del && i.icmp.uCompare(ukey, i.key) < 0 {
|
||||
return true
|
||||
}
|
||||
del = (t == tDel)
|
||||
@@ -252,7 +269,7 @@ func (i *dbIter) Prev() bool {
|
||||
for i.iter.Prev() {
|
||||
ukey, _, _, ok := parseIkey(i.iter.Key())
|
||||
if ok {
|
||||
if i.cmp.Compare(ukey, i.key) < 0 {
|
||||
if i.icmp.uCompare(ukey, i.key) < 0 {
|
||||
goto cont
|
||||
}
|
||||
} else if i.strict {
|
||||
@@ -290,6 +307,7 @@ func (i *dbIter) Release() {
|
||||
|
||||
if i.releaser != nil {
|
||||
i.releaser.Release()
|
||||
i.releaser = nil
|
||||
}
|
||||
|
||||
i.dir = dirReleased
|
||||
@@ -297,13 +315,19 @@ func (i *dbIter) Release() {
|
||||
i.value = nil
|
||||
i.iter.Release()
|
||||
i.iter = nil
|
||||
atomic.AddInt32(&i.db.aliveIters, -1)
|
||||
i.db = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *dbIter) SetReleaser(releaser util.Releaser) {
|
||||
if i.dir != dirReleased {
|
||||
i.releaser = releaser
|
||||
if i.dir == dirReleased {
|
||||
panic(util.ErrReleased)
|
||||
}
|
||||
if i.releaser != nil && releaser != nil {
|
||||
panic(util.ErrHasReleaser)
|
||||
}
|
||||
i.releaser = releaser
|
||||
}
|
||||
|
||||
func (i *dbIter) Error() error {
|
||||
|
||||
+32
-28
@@ -9,6 +9,7 @@ package leveldb
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
@@ -81,18 +82,19 @@ func (db *DB) minSeq() uint64 {
|
||||
type Snapshot struct {
|
||||
db *DB
|
||||
elem *snapshotElement
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
released bool
|
||||
}
|
||||
|
||||
// Creates new snapshot object.
|
||||
func (db *DB) newSnapshot() *Snapshot {
|
||||
p := &Snapshot{
|
||||
snap := &Snapshot{
|
||||
db: db,
|
||||
elem: db.acquireSnapshot(),
|
||||
}
|
||||
runtime.SetFinalizer(p, (*Snapshot).Release)
|
||||
return p
|
||||
atomic.AddInt32(&db.aliveSnaps, 1)
|
||||
runtime.SetFinalizer(snap, (*Snapshot).Release)
|
||||
return snap
|
||||
}
|
||||
|
||||
// Get gets the value for the given key. It returns ErrNotFound if
|
||||
@@ -100,19 +102,18 @@ func (db *DB) newSnapshot() *Snapshot {
|
||||
//
|
||||
// The caller should not modify the contents of the returned slice, but
|
||||
// it is safe to modify the contents of the argument after Get returns.
|
||||
func (p *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
db := p.db
|
||||
err = db.ok()
|
||||
func (snap *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
err = snap.db.ok()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.released {
|
||||
snap.mu.RLock()
|
||||
defer snap.mu.RUnlock()
|
||||
if snap.released {
|
||||
err = ErrSnapshotReleased
|
||||
return
|
||||
}
|
||||
return db.get(key, p.elem.seq, ro)
|
||||
return snap.db.get(key, snap.elem.seq, ro)
|
||||
}
|
||||
|
||||
// NewIterator returns an iterator for the snapshot of the uderlying DB.
|
||||
@@ -132,17 +133,18 @@ func (p *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error
|
||||
// iterator would be still valid until released.
|
||||
//
|
||||
// Also read Iterator documentation of the leveldb/iterator package.
|
||||
func (p *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
db := p.db
|
||||
if err := db.ok(); err != nil {
|
||||
func (snap *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
if err := snap.db.ok(); err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.released {
|
||||
snap.mu.Lock()
|
||||
defer snap.mu.Unlock()
|
||||
if snap.released {
|
||||
return iterator.NewEmptyIterator(ErrSnapshotReleased)
|
||||
}
|
||||
return db.newIterator(p.elem.seq, slice, ro)
|
||||
// Since iterator already hold version ref, it doesn't need to
|
||||
// hold snapshot ref.
|
||||
return snap.db.newIterator(snap.elem.seq, slice, ro)
|
||||
}
|
||||
|
||||
// Release releases the snapshot. This will not release any returned
|
||||
@@ -150,16 +152,18 @@ func (p *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.
|
||||
// underlying DB is closed.
|
||||
//
|
||||
// Other methods should not be called after the snapshot has been released.
|
||||
func (p *Snapshot) Release() {
|
||||
p.mu.Lock()
|
||||
if !p.released {
|
||||
// Clear the finalizer.
|
||||
runtime.SetFinalizer(p, nil)
|
||||
func (snap *Snapshot) Release() {
|
||||
snap.mu.Lock()
|
||||
defer snap.mu.Unlock()
|
||||
|
||||
p.released = true
|
||||
p.db.releaseSnapshot(p.elem)
|
||||
p.db = nil
|
||||
p.elem = nil
|
||||
if !snap.released {
|
||||
// Clear the finalizer.
|
||||
runtime.SetFinalizer(snap, nil)
|
||||
|
||||
snap.released = true
|
||||
snap.db.releaseSnapshot(snap.elem)
|
||||
atomic.AddInt32(&snap.db.aliveSnaps, -1)
|
||||
snap.db = nil
|
||||
snap.elem = nil
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
+143
-55
@@ -8,106 +8,194 @@ package leveldb
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/journal"
|
||||
"github.com/syndtr/goleveldb/leveldb/memdb"
|
||||
)
|
||||
|
||||
type memDB struct {
|
||||
db *DB
|
||||
mdb *memdb.DB
|
||||
ref int32
|
||||
}
|
||||
|
||||
func (m *memDB) incref() {
|
||||
atomic.AddInt32(&m.ref, 1)
|
||||
}
|
||||
|
||||
func (m *memDB) decref() {
|
||||
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
|
||||
// Only put back memdb with std capacity.
|
||||
if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() {
|
||||
m.mdb.Reset()
|
||||
m.db.mpoolPut(m.mdb)
|
||||
}
|
||||
m.db = nil
|
||||
m.mdb = nil
|
||||
} else if ref < 0 {
|
||||
panic("negative memdb ref")
|
||||
}
|
||||
}
|
||||
|
||||
// Get latest sequence number.
|
||||
func (d *DB) getSeq() uint64 {
|
||||
return atomic.LoadUint64(&d.seq)
|
||||
func (db *DB) getSeq() uint64 {
|
||||
return atomic.LoadUint64(&db.seq)
|
||||
}
|
||||
|
||||
// Atomically adds delta to seq.
|
||||
func (d *DB) addSeq(delta uint64) {
|
||||
atomic.AddUint64(&d.seq, delta)
|
||||
func (db *DB) addSeq(delta uint64) {
|
||||
atomic.AddUint64(&db.seq, delta)
|
||||
}
|
||||
|
||||
func (db *DB) mpoolPut(mem *memdb.DB) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
select {
|
||||
case db.memPool <- mem:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) mpoolGet() *memdb.DB {
|
||||
select {
|
||||
case mem := <-db.memPool:
|
||||
return mem
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) mpoolDrain() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
select {
|
||||
case <-db.memPool:
|
||||
default:
|
||||
}
|
||||
case _, _ = <-db.closeC:
|
||||
close(db.memPool)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new memdb and froze the old one; need external synchronization.
|
||||
// newMem only called synchronously by the writer.
|
||||
func (d *DB) newMem(n int) (mem *memdb.DB, err error) {
|
||||
s := d.s
|
||||
|
||||
num := s.allocFileNum()
|
||||
file := s.getJournalFile(num)
|
||||
func (db *DB) newMem(n int) (mem *memDB, err error) {
|
||||
num := db.s.allocFileNum()
|
||||
file := db.s.getJournalFile(num)
|
||||
w, err := file.Create()
|
||||
if err != nil {
|
||||
s.reuseFileNum(num)
|
||||
db.s.reuseFileNum(num)
|
||||
return
|
||||
}
|
||||
d.memMu.Lock()
|
||||
if d.journal == nil {
|
||||
d.journal = journal.NewWriter(w)
|
||||
} else {
|
||||
d.journal.Reset(w)
|
||||
d.journalWriter.Close()
|
||||
d.frozenJournalFile = d.journalFile
|
||||
|
||||
db.memMu.Lock()
|
||||
defer db.memMu.Unlock()
|
||||
|
||||
if db.frozenMem != nil {
|
||||
panic("still has frozen mem")
|
||||
}
|
||||
d.journalWriter = w
|
||||
d.journalFile = file
|
||||
d.frozenMem = d.mem
|
||||
d.mem = memdb.New(s.cmp, maxInt(d.s.o.GetWriteBuffer(), n))
|
||||
mem = d.mem
|
||||
// The seq only incremented by the writer.
|
||||
d.frozenSeq = d.seq
|
||||
d.memMu.Unlock()
|
||||
|
||||
if db.journal == nil {
|
||||
db.journal = journal.NewWriter(w)
|
||||
} else {
|
||||
db.journal.Reset(w)
|
||||
db.journalWriter.Close()
|
||||
db.frozenJournalFile = db.journalFile
|
||||
}
|
||||
db.journalWriter = w
|
||||
db.journalFile = file
|
||||
db.frozenMem = db.mem
|
||||
mdb := db.mpoolGet()
|
||||
if mdb == nil || mdb.Capacity() < n {
|
||||
mdb = memdb.New(db.s.icmp, maxInt(db.s.o.GetWriteBuffer(), n))
|
||||
}
|
||||
mem = &memDB{
|
||||
db: db,
|
||||
mdb: mdb,
|
||||
ref: 2,
|
||||
}
|
||||
db.mem = mem
|
||||
// The seq only incremented by the writer. And whoever called newMem
|
||||
// should hold write lock, so no need additional synchronization here.
|
||||
db.frozenSeq = db.seq
|
||||
return
|
||||
}
|
||||
|
||||
// Get all memdbs.
|
||||
func (d *DB) getMems() (e *memdb.DB, f *memdb.DB) {
|
||||
d.memMu.RLock()
|
||||
defer d.memMu.RUnlock()
|
||||
return d.mem, d.frozenMem
|
||||
func (db *DB) getMems() (e, f *memDB) {
|
||||
db.memMu.RLock()
|
||||
defer db.memMu.RUnlock()
|
||||
if db.mem == nil {
|
||||
panic("nil effective mem")
|
||||
}
|
||||
db.mem.incref()
|
||||
if db.frozenMem != nil {
|
||||
db.frozenMem.incref()
|
||||
}
|
||||
return db.mem, db.frozenMem
|
||||
}
|
||||
|
||||
// Get frozen memdb.
|
||||
func (d *DB) getEffectiveMem() *memdb.DB {
|
||||
d.memMu.RLock()
|
||||
defer d.memMu.RUnlock()
|
||||
return d.mem
|
||||
func (db *DB) getEffectiveMem() *memDB {
|
||||
db.memMu.RLock()
|
||||
defer db.memMu.RUnlock()
|
||||
if db.mem == nil {
|
||||
panic("nil effective mem")
|
||||
}
|
||||
db.mem.incref()
|
||||
return db.mem
|
||||
}
|
||||
|
||||
// Check whether we has frozen memdb.
|
||||
func (d *DB) hasFrozenMem() bool {
|
||||
d.memMu.RLock()
|
||||
defer d.memMu.RUnlock()
|
||||
return d.frozenMem != nil
|
||||
func (db *DB) hasFrozenMem() bool {
|
||||
db.memMu.RLock()
|
||||
defer db.memMu.RUnlock()
|
||||
return db.frozenMem != nil
|
||||
}
|
||||
|
||||
// Get frozen memdb.
|
||||
func (d *DB) getFrozenMem() *memdb.DB {
|
||||
d.memMu.RLock()
|
||||
defer d.memMu.RUnlock()
|
||||
return d.frozenMem
|
||||
func (db *DB) getFrozenMem() *memDB {
|
||||
db.memMu.RLock()
|
||||
defer db.memMu.RUnlock()
|
||||
if db.frozenMem != nil {
|
||||
db.frozenMem.incref()
|
||||
}
|
||||
return db.frozenMem
|
||||
}
|
||||
|
||||
// Drop frozen memdb; assume that frozen memdb isn't nil.
|
||||
func (d *DB) dropFrozenMem() {
|
||||
d.memMu.Lock()
|
||||
if err := d.frozenJournalFile.Remove(); err != nil {
|
||||
d.s.logf("journal@remove removing @%d %q", d.frozenJournalFile.Num(), err)
|
||||
func (db *DB) dropFrozenMem() {
|
||||
db.memMu.Lock()
|
||||
if err := db.frozenJournalFile.Remove(); err != nil {
|
||||
db.logf("journal@remove removing @%d %q", db.frozenJournalFile.Num(), err)
|
||||
} else {
|
||||
d.s.logf("journal@remove removed @%d", d.frozenJournalFile.Num())
|
||||
db.logf("journal@remove removed @%d", db.frozenJournalFile.Num())
|
||||
}
|
||||
d.frozenJournalFile = nil
|
||||
d.frozenMem = nil
|
||||
d.memMu.Unlock()
|
||||
db.frozenJournalFile = nil
|
||||
db.frozenMem.decref()
|
||||
db.frozenMem = nil
|
||||
db.memMu.Unlock()
|
||||
}
|
||||
|
||||
// Set closed flag; return true if not already closed.
|
||||
func (d *DB) setClosed() bool {
|
||||
return atomic.CompareAndSwapUint32(&d.closed, 0, 1)
|
||||
func (db *DB) setClosed() bool {
|
||||
return atomic.CompareAndSwapUint32(&db.closed, 0, 1)
|
||||
}
|
||||
|
||||
// Check whether DB was closed.
|
||||
func (d *DB) isClosed() bool {
|
||||
return atomic.LoadUint32(&d.closed) != 0
|
||||
func (db *DB) isClosed() bool {
|
||||
return atomic.LoadUint32(&db.closed) != 0
|
||||
}
|
||||
|
||||
// Check read ok status.
|
||||
func (d *DB) ok() error {
|
||||
if d.isClosed() {
|
||||
func (db *DB) ok() error {
|
||||
if db.isClosed() {
|
||||
return ErrClosed
|
||||
}
|
||||
return nil
|
||||
|
||||
+132
-76
@@ -124,13 +124,15 @@ func (h *dbHarness) openAssert(want bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbHarness) put(key, value string) {
|
||||
t := h.t
|
||||
db := h.db
|
||||
func (h *dbHarness) write(batch *Batch) {
|
||||
if err := h.db.Write(batch, h.wo); err != nil {
|
||||
h.t.Error("Write: got error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Put([]byte(key), []byte(value), h.wo)
|
||||
if err != nil {
|
||||
t.Error("Put: got error: ", err)
|
||||
func (h *dbHarness) put(key, value string) {
|
||||
if err := h.db.Put([]byte(key), []byte(value), h.wo); err != nil {
|
||||
h.t.Error("Put: got error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,15 +149,12 @@ func (h *dbHarness) maxNextLevelOverlappingBytes(want uint64) {
|
||||
db := h.db
|
||||
|
||||
var res uint64
|
||||
ucmp := db.s.cmp.cmp
|
||||
v := db.s.version()
|
||||
for i, tt := range v.tables[1 : len(v.tables)-1] {
|
||||
level := i + 1
|
||||
next := v.tables[level+1]
|
||||
for _, t := range tt {
|
||||
var r tFiles
|
||||
min, max := t.min.ukey(), t.max.ukey()
|
||||
next.getOverlaps(min, max, &r, true, ucmp)
|
||||
r := next.getOverlaps(nil, db.s.icmp, t.imin.ukey(), t.imax.ukey(), false)
|
||||
sum := r.size()
|
||||
if sum > res {
|
||||
res = sum
|
||||
@@ -179,6 +178,21 @@ func (h *dbHarness) delete(key string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbHarness) assertNumKeys(want int) {
|
||||
iter := h.db.NewIterator(nil, h.ro)
|
||||
defer iter.Release()
|
||||
got := 0
|
||||
for iter.Next() {
|
||||
got++
|
||||
}
|
||||
if err := iter.Error(); err != nil {
|
||||
h.t.Error("assertNumKeys: ", err)
|
||||
}
|
||||
if want != got {
|
||||
h.t.Errorf("assertNumKeys: want=%d got=%d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbHarness) getr(db Reader, key string, expectFound bool) (found bool, v []byte) {
|
||||
t := h.t
|
||||
v, err := db.Get([]byte(key), h.ro)
|
||||
@@ -221,7 +235,7 @@ func (h *dbHarness) getVal(key, value string) {
|
||||
func (h *dbHarness) allEntriesFor(key, want string) {
|
||||
t := h.t
|
||||
db := h.db
|
||||
ucmp := db.s.cmp.cmp
|
||||
s := db.s
|
||||
|
||||
ikey := newIKey([]byte(key), kMaxSeq, tVal)
|
||||
iter := db.newRawIterator(nil, nil)
|
||||
@@ -234,7 +248,7 @@ func (h *dbHarness) allEntriesFor(key, want string) {
|
||||
for iter.Valid() {
|
||||
rkey := iKey(iter.Key())
|
||||
if _, t, ok := rkey.parseNum(); ok {
|
||||
if ucmp.Compare(ikey.ukey(), rkey.ukey()) != 0 {
|
||||
if s.icmp.uCompare(ikey.ukey(), rkey.ukey()) != 0 {
|
||||
break
|
||||
}
|
||||
if !first {
|
||||
@@ -291,7 +305,16 @@ func (h *dbHarness) getKeyVal(want string) {
|
||||
func (h *dbHarness) waitCompaction() {
|
||||
t := h.t
|
||||
db := h.db
|
||||
if err := db.wakeCompaction(2); err != nil {
|
||||
if err := db.compSendIdle(db.tcompCmdC); err != nil {
|
||||
t.Error("compaction error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dbHarness) waitMemCompaction() {
|
||||
t := h.t
|
||||
db := h.db
|
||||
|
||||
if err := db.compSendIdle(db.mcompCmdC); err != nil {
|
||||
t.Error("compaction error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -300,41 +323,15 @@ func (h *dbHarness) compactMem() {
|
||||
t := h.t
|
||||
db := h.db
|
||||
|
||||
if err := db.wakeCompaction(1); err != nil {
|
||||
db.writeLockC <- struct{}{}
|
||||
defer func() {
|
||||
<-db.writeLockC
|
||||
}()
|
||||
|
||||
if _, err := db.rotateMem(0); err != nil {
|
||||
t.Error("compaction error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if mem := db.getEffectiveMem(); mem.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-db.compMemAckCh:
|
||||
case err := <-db.compErrCh:
|
||||
t.Error("compaction error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// create new memdb and journal
|
||||
_, err := db.newMem(0)
|
||||
if err != nil {
|
||||
t.Error("newMem: got error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cch := make(chan struct{})
|
||||
// Schedule mem compaction.
|
||||
select {
|
||||
case db.compMemCh <- (chan<- struct{})(cch):
|
||||
case err := <-db.compErrCh:
|
||||
t.Error("compaction error: ", err)
|
||||
return
|
||||
}
|
||||
// Wait.
|
||||
select {
|
||||
case <-cch:
|
||||
case err := <-db.compErrCh:
|
||||
if err := db.compSendIdle(db.mcompCmdC); err != nil {
|
||||
t.Error("compaction error: ", err)
|
||||
}
|
||||
|
||||
@@ -347,35 +344,22 @@ func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool)
|
||||
t := h.t
|
||||
db := h.db
|
||||
|
||||
cch := make(chan struct{})
|
||||
req := &cReq{level: level, cch: cch}
|
||||
var _min, _max []byte
|
||||
if min != "" {
|
||||
req.min = []byte(min)
|
||||
_min = []byte(min)
|
||||
}
|
||||
if max != "" {
|
||||
req.max = []byte(max)
|
||||
_max = []byte(max)
|
||||
}
|
||||
|
||||
// Push manual compaction request.
|
||||
select {
|
||||
case err := <-db.compErrCh:
|
||||
t.Error("CompactRangeAt: compaction error: ", err)
|
||||
return
|
||||
case db.compReqCh <- req:
|
||||
}
|
||||
|
||||
// Wait for compaction
|
||||
select {
|
||||
case err := <-db.compErrCh:
|
||||
if err := db.compSendRange(db.tcompCmdC, level, _min, _max); err != nil {
|
||||
if wanterr {
|
||||
t.Log("CompactRangeAt: got error (expected): ", err)
|
||||
} else {
|
||||
t.Error("CompactRangeAt: got error: ", err)
|
||||
}
|
||||
case <-cch:
|
||||
if wanterr {
|
||||
t.Error("CompactRangeAt: expect error")
|
||||
}
|
||||
} else if wanterr {
|
||||
t.Error("CompactRangeAt: expect error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +378,7 @@ func (h *dbHarness) compactRange(min, max string) {
|
||||
if max != "" {
|
||||
r.Limit = []byte(max)
|
||||
}
|
||||
err := db.CompactRange(r)
|
||||
if err != nil {
|
||||
if err := db.CompactRange(r); err != nil {
|
||||
t.Error("CompactRange: got error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -404,11 +387,11 @@ func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
|
||||
t := h.t
|
||||
db := h.db
|
||||
|
||||
s, err := db.GetApproximateSizes([]util.Range{
|
||||
s, err := db.SizeOf([]util.Range{
|
||||
{[]byte(start), []byte(limit)},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("GetApproximateSizes: got error: ", err)
|
||||
t.Error("SizeOf: got error: ", err)
|
||||
}
|
||||
if s.Sum() < low || s.Sum() > hi {
|
||||
t.Errorf("sizeof %q to %q not in range, want %d - %d, got %d",
|
||||
@@ -1008,6 +991,7 @@ func TestDb_SparseMerge(t *testing.T) {
|
||||
h.put("C", "vc")
|
||||
h.compactMem()
|
||||
h.compactRangeAt(0, "", "")
|
||||
h.waitCompaction()
|
||||
|
||||
// Make sparse update
|
||||
h.put("A", "va2")
|
||||
@@ -1017,12 +1001,14 @@ func TestDb_SparseMerge(t *testing.T) {
|
||||
|
||||
h.maxNextLevelOverlappingBytes(20 * 1048576)
|
||||
h.compactRangeAt(0, "", "")
|
||||
h.waitCompaction()
|
||||
h.maxNextLevelOverlappingBytes(20 * 1048576)
|
||||
h.compactRangeAt(1, "", "")
|
||||
h.waitCompaction()
|
||||
h.maxNextLevelOverlappingBytes(20 * 1048576)
|
||||
}
|
||||
|
||||
func TestDb_ApproximateSizes(t *testing.T) {
|
||||
func TestDb_SizeOf(t *testing.T) {
|
||||
h := newDbHarnessWopt(t, &opt.Options{
|
||||
Compression: opt.NoCompression,
|
||||
WriteBuffer: 10000000,
|
||||
@@ -1042,7 +1028,7 @@ func TestDb_ApproximateSizes(t *testing.T) {
|
||||
h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), s1/10))
|
||||
}
|
||||
|
||||
// 0 because GetApproximateSizes() does not account for memtable space
|
||||
// 0 because SizeOf() does not account for memtable space
|
||||
h.sizeAssert("", numKey(50), 0, 0)
|
||||
|
||||
for r := 0; r < 3; r++ {
|
||||
@@ -1072,7 +1058,7 @@ func TestDb_ApproximateSizes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDb_ApproximateSizes_MixOfSmallAndLarge(t *testing.T) {
|
||||
func TestDb_SizeOf_MixOfSmallAndLarge(t *testing.T) {
|
||||
h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression})
|
||||
defer h.close()
|
||||
|
||||
@@ -1224,7 +1210,7 @@ func TestDb_DeletionMarkers2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDb_CompactionTableOpenError(t *testing.T) {
|
||||
h := newDbHarnessWopt(t, &opt.Options{MaxOpenFiles: 0})
|
||||
h := newDbHarnessWopt(t, &opt.Options{CachedOpenFiles: -1})
|
||||
defer h.close()
|
||||
|
||||
im := 10
|
||||
@@ -1244,7 +1230,7 @@ func TestDb_CompactionTableOpenError(t *testing.T) {
|
||||
|
||||
h.stor.SetOpenErr(storage.TypeTable)
|
||||
go h.db.CompactRange(util.Range{})
|
||||
if err := h.db.wakeCompaction(2); err != nil {
|
||||
if err := h.db.compSendIdle(h.db.tcompCmdC); err != nil {
|
||||
t.Log("compaction error: ", err)
|
||||
}
|
||||
h.closeDB0()
|
||||
@@ -1486,7 +1472,7 @@ func TestDb_ClosedIsClosed(t *testing.T) {
|
||||
_, err = db.GetProperty("leveldb.stats")
|
||||
assertErr(t, err, true)
|
||||
|
||||
_, err = db.GetApproximateSizes([]util.Range{{[]byte("a"), []byte("z")}})
|
||||
_, err = db.SizeOf([]util.Range{{[]byte("a"), []byte("z")}})
|
||||
assertErr(t, err, true)
|
||||
|
||||
assertErr(t, db.CompactRange(util.Range{}), true)
|
||||
@@ -1591,7 +1577,7 @@ func TestDb_BloomFilter(t *testing.T) {
|
||||
return fmt.Sprintf("key%06d", i)
|
||||
}
|
||||
|
||||
n := 10000
|
||||
const n = 10000
|
||||
|
||||
// Populate multiple layers
|
||||
for i := 0; i < n; i++ {
|
||||
@@ -1828,3 +1814,73 @@ func TestDb_DeletionMarkersOnMemdb(t *testing.T) {
|
||||
h.get("foo", false)
|
||||
h.getKeyVal("")
|
||||
}
|
||||
|
||||
func TestDb_LeveldbIssue178(t *testing.T) {
|
||||
nKeys := (kMaxTableSize / 30) * 5
|
||||
key1 := func(i int) string {
|
||||
return fmt.Sprintf("my_key_%d", i)
|
||||
}
|
||||
key2 := func(i int) string {
|
||||
return fmt.Sprintf("my_key_%d_xxx", i)
|
||||
}
|
||||
|
||||
// Disable compression since it affects the creation of layers and the
|
||||
// code below is trying to test against a very specific scenario.
|
||||
h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression})
|
||||
defer h.close()
|
||||
|
||||
// Create first key range.
|
||||
batch := new(Batch)
|
||||
for i := 0; i < nKeys; i++ {
|
||||
batch.Put([]byte(key1(i)), []byte("value for range 1 key"))
|
||||
}
|
||||
h.write(batch)
|
||||
|
||||
// Create second key range.
|
||||
batch.Reset()
|
||||
for i := 0; i < nKeys; i++ {
|
||||
batch.Put([]byte(key2(i)), []byte("value for range 2 key"))
|
||||
}
|
||||
h.write(batch)
|
||||
|
||||
// Delete second key range.
|
||||
batch.Reset()
|
||||
for i := 0; i < nKeys; i++ {
|
||||
batch.Delete([]byte(key2(i)))
|
||||
}
|
||||
h.write(batch)
|
||||
h.waitMemCompaction()
|
||||
|
||||
// Run manual compaction.
|
||||
h.compactRange(key1(0), key1(nKeys-1))
|
||||
|
||||
// Checking the keys.
|
||||
h.assertNumKeys(nKeys)
|
||||
}
|
||||
|
||||
func TestDb_LeveldbIssue200(t *testing.T) {
|
||||
h := newDbHarness(t)
|
||||
defer h.close()
|
||||
|
||||
h.put("1", "b")
|
||||
h.put("2", "c")
|
||||
h.put("3", "d")
|
||||
h.put("4", "e")
|
||||
h.put("5", "f")
|
||||
|
||||
iter := h.db.NewIterator(nil, h.ro)
|
||||
|
||||
// Add an element that should not be reflected in the iterator.
|
||||
h.put("25", "cd")
|
||||
|
||||
iter.Seek([]byte("5"))
|
||||
assertBytes(t, []byte("5"), iter.Key())
|
||||
iter.Prev()
|
||||
assertBytes(t, []byte("4"), iter.Key())
|
||||
iter.Prev()
|
||||
assertBytes(t, []byte("3"), iter.Key())
|
||||
iter.Next()
|
||||
assertBytes(t, []byte("4"), iter.Key())
|
||||
iter.Next()
|
||||
assertBytes(t, []byte("5"), iter.Key())
|
||||
}
|
||||
|
||||
+37
-17
@@ -7,6 +7,8 @@
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
@@ -30,45 +32,63 @@ func (p Sizes) Sum() (n uint64) {
|
||||
return n
|
||||
}
|
||||
|
||||
// Remove unused files.
|
||||
func (d *DB) cleanFiles() error {
|
||||
s := d.s
|
||||
// Logging.
|
||||
func (db *DB) log(v ...interface{}) { db.s.log(v...) }
|
||||
func (db *DB) logf(format string, v ...interface{}) { db.s.logf(format, v...) }
|
||||
|
||||
v := s.version_NB()
|
||||
tables := make(map[uint64]struct{})
|
||||
for _, tt := range v.tables {
|
||||
for _, t := range tt {
|
||||
tables[t.file.Num()] = struct{}{}
|
||||
// Check and clean files.
|
||||
func (db *DB) checkAndCleanFiles() error {
|
||||
v := db.s.version_NB()
|
||||
tablesMap := make(map[uint64]bool)
|
||||
for _, tables := range v.tables {
|
||||
for _, t := range tables {
|
||||
tablesMap[t.file.Num()] = false
|
||||
}
|
||||
}
|
||||
|
||||
ff, err := s.getFiles(storage.TypeAll)
|
||||
files, err := db.s.getFiles(storage.TypeAll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var nTables int
|
||||
var rem []storage.File
|
||||
for _, f := range ff {
|
||||
for _, f := range files {
|
||||
keep := true
|
||||
switch f.Type() {
|
||||
case storage.TypeManifest:
|
||||
keep = f.Num() >= s.manifestFile.Num()
|
||||
keep = f.Num() >= db.s.manifestFile.Num()
|
||||
case storage.TypeJournal:
|
||||
if d.frozenJournalFile != nil {
|
||||
keep = f.Num() >= d.frozenJournalFile.Num()
|
||||
if db.frozenJournalFile != nil {
|
||||
keep = f.Num() >= db.frozenJournalFile.Num()
|
||||
} else {
|
||||
keep = f.Num() >= d.journalFile.Num()
|
||||
keep = f.Num() >= db.journalFile.Num()
|
||||
}
|
||||
case storage.TypeTable:
|
||||
_, keep = tables[f.Num()]
|
||||
_, keep = tablesMap[f.Num()]
|
||||
if keep {
|
||||
tablesMap[f.Num()] = true
|
||||
nTables++
|
||||
}
|
||||
}
|
||||
|
||||
if !keep {
|
||||
rem = append(rem, f)
|
||||
}
|
||||
}
|
||||
s.logf("db@janitor F·%d G·%d", len(ff), len(rem))
|
||||
|
||||
if nTables != len(tablesMap) {
|
||||
for num, present := range tablesMap {
|
||||
if !present {
|
||||
db.logf("db@janitor table missing @%d", num)
|
||||
}
|
||||
}
|
||||
return ErrCorrupted{Type: MissingFiles, Err: errors.New("leveldb: table files missing")}
|
||||
}
|
||||
|
||||
db.logf("db@janitor F·%d G·%d", len(files), len(rem))
|
||||
for _, f := range rem {
|
||||
s.logf("db@janitor removing %s-%d", f.Type(), f.Num())
|
||||
db.logf("db@janitor removing %s-%d", f.Type(), f.Num())
|
||||
if err := f.Remove(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+131
-65
@@ -11,48 +11,71 @@ import (
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/memdb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
func (d *DB) doWriteJournal(b *Batch) error {
|
||||
w, err := d.journal.Next()
|
||||
func (db *DB) writeJournal(b *Batch) error {
|
||||
w, err := db.journal.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(b.encode()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.journal.Flush(); err != nil {
|
||||
if err := db.journal.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.sync {
|
||||
return d.journalWriter.Sync()
|
||||
return db.journalWriter.Sync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) writeJournal() {
|
||||
defer d.closeWg.Done()
|
||||
func (db *DB) jWriter() {
|
||||
defer db.closeW.Done()
|
||||
for {
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return
|
||||
case b := <-d.journalCh:
|
||||
case b := <-db.journalC:
|
||||
if b != nil {
|
||||
d.journalAckCh <- d.doWriteJournal(b)
|
||||
db.journalAckC <- db.writeJournal(b)
|
||||
}
|
||||
case _, _ = <-db.closeC:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) {
|
||||
s := d.s
|
||||
func (db *DB) rotateMem(n int) (mem *memDB, err error) {
|
||||
// Wait for pending memdb compaction.
|
||||
err = db.compSendIdle(db.mcompCmdC)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new memdb and journal.
|
||||
mem, err = db.newMem(n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Schedule memdb compaction.
|
||||
db.compTrigger(db.mcompTriggerC)
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
||||
delayed := false
|
||||
flush := func() bool {
|
||||
v := s.version()
|
||||
flush := func() (retry bool) {
|
||||
v := db.s.version()
|
||||
defer v.release()
|
||||
mem = d.getEffectiveMem()
|
||||
nn = mem.Free()
|
||||
mem = db.getEffectiveMem()
|
||||
defer func() {
|
||||
if retry {
|
||||
mem.decref()
|
||||
mem = nil
|
||||
}
|
||||
}()
|
||||
nn = mem.mdb.Free()
|
||||
switch {
|
||||
case v.tLen(0) >= kL0_SlowdownWritesTrigger && !delayed:
|
||||
delayed = true
|
||||
@@ -61,33 +84,23 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) {
|
||||
return false
|
||||
case v.tLen(0) >= kL0_StopWritesTrigger:
|
||||
delayed = true
|
||||
err = d.wakeCompaction(2)
|
||||
err = db.compSendIdle(db.tcompCmdC)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
// Allow memdb to grow if it has no entry.
|
||||
if mem.Len() == 0 {
|
||||
if mem.mdb.Len() == 0 {
|
||||
nn = n
|
||||
return false
|
||||
} else {
|
||||
mem.decref()
|
||||
mem, err = db.rotateMem(n)
|
||||
if err == nil {
|
||||
nn = mem.mdb.Free()
|
||||
} else {
|
||||
nn = 0
|
||||
}
|
||||
}
|
||||
// Wait for pending memdb compaction.
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
err = ErrClosed
|
||||
return false
|
||||
case <-d.compMemAckCh:
|
||||
case err = <-d.compErrCh:
|
||||
return false
|
||||
}
|
||||
// Create new memdb and journal.
|
||||
mem, err = d.newMem(n)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Schedule memdb compaction.
|
||||
d.compMemCh <- nil
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -96,7 +109,7 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) {
|
||||
for flush() {
|
||||
}
|
||||
if delayed {
|
||||
s.logf("db@write delayed T·%v", time.Since(start))
|
||||
db.logf("db@write delayed T·%v", time.Since(start))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -105,8 +118,8 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) {
|
||||
// sequentially.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after Write returns.
|
||||
func (d *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
||||
err = d.ok()
|
||||
func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
||||
err = db.ok()
|
||||
if err != nil || b == nil || b.len() == 0 {
|
||||
return
|
||||
}
|
||||
@@ -116,28 +129,29 @@ func (d *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
||||
// The write happen synchronously.
|
||||
retry:
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
return ErrClosed
|
||||
case d.writeCh <- b:
|
||||
if <-d.writeMergedCh {
|
||||
return <-d.writeAckCh
|
||||
case db.writeC <- b:
|
||||
if <-db.writeMergedC {
|
||||
return <-db.writeAckC
|
||||
}
|
||||
goto retry
|
||||
case d.writeLockCh <- struct{}{}:
|
||||
case db.writeLockC <- struct{}{}:
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
merged := 0
|
||||
defer func() {
|
||||
<-d.writeLockCh
|
||||
<-db.writeLockC
|
||||
for i := 0; i < merged; i++ {
|
||||
d.writeAckCh <- err
|
||||
db.writeAckC <- err
|
||||
}
|
||||
}()
|
||||
|
||||
mem, memFree, err := d.flush(b.size())
|
||||
mem, memFree, err := db.flush(b.size())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer mem.decref()
|
||||
|
||||
// Calculate maximum size of the batch.
|
||||
m := 1 << 20
|
||||
@@ -150,13 +164,13 @@ retry:
|
||||
drain:
|
||||
for b.size() < m && !b.sync {
|
||||
select {
|
||||
case nb := <-d.writeCh:
|
||||
case nb := <-db.writeC:
|
||||
if b.size()+nb.size() <= m {
|
||||
b.append(nb)
|
||||
d.writeMergedCh <- true
|
||||
db.writeMergedC <- true
|
||||
merged++
|
||||
} else {
|
||||
d.writeMergedCh <- false
|
||||
db.writeMergedC <- false
|
||||
break drain
|
||||
}
|
||||
default:
|
||||
@@ -165,41 +179,45 @@ drain:
|
||||
}
|
||||
|
||||
// Set batch first seq number relative from last seq.
|
||||
b.seq = d.seq + 1
|
||||
b.seq = db.seq + 1
|
||||
|
||||
// Write journal concurrently if it is large enough.
|
||||
if b.size() >= (128 << 10) {
|
||||
// Push the write batch to the journal writer
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
case _, _ = <-db.closeC:
|
||||
err = ErrClosed
|
||||
return
|
||||
case d.journalCh <- b:
|
||||
case db.journalC <- b:
|
||||
// Write into memdb
|
||||
b.memReplay(mem)
|
||||
b.memReplay(mem.mdb)
|
||||
}
|
||||
// Wait for journal writer
|
||||
select {
|
||||
case _, _ = <-d.closeCh:
|
||||
case _, _ = <-db.closeC:
|
||||
err = ErrClosed
|
||||
return
|
||||
case err = <-d.journalAckCh:
|
||||
case err = <-db.journalAckC:
|
||||
if err != nil {
|
||||
// Revert memdb if error detected
|
||||
b.revertMemReplay(mem)
|
||||
b.revertMemReplay(mem.mdb)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = d.doWriteJournal(b)
|
||||
err = db.writeJournal(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b.memReplay(mem)
|
||||
b.memReplay(mem.mdb)
|
||||
}
|
||||
|
||||
// Set last seq number.
|
||||
d.addSeq(uint64(b.len()))
|
||||
db.addSeq(uint64(b.len()))
|
||||
|
||||
if b.size() >= memFree {
|
||||
db.rotateMem(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,18 +225,66 @@ drain:
|
||||
// for that key; a DB is not a multi-map.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after Put returns.
|
||||
func (d *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
|
||||
func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
|
||||
b := new(Batch)
|
||||
b.Put(key, value)
|
||||
return d.Write(b, wo)
|
||||
return db.Write(b, wo)
|
||||
}
|
||||
|
||||
// Delete deletes the value for the given key. It returns ErrNotFound if
|
||||
// the DB does not contain the key.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after Delete returns.
|
||||
func (d *DB) Delete(key []byte, wo *opt.WriteOptions) error {
|
||||
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
|
||||
b := new(Batch)
|
||||
b.Delete(key)
|
||||
return d.Write(b, wo)
|
||||
return db.Write(b, wo)
|
||||
}
|
||||
|
||||
func isMemOverlaps(icmp *iComparer, mem *memdb.DB, min, max []byte) bool {
|
||||
iter := mem.NewIterator(nil)
|
||||
defer iter.Release()
|
||||
return (max == nil || (iter.First() && icmp.uCompare(max, iKey(iter.Key()).ukey()) >= 0)) &&
|
||||
(min == nil || (iter.Last() && icmp.uCompare(min, iKey(iter.Key()).ukey()) <= 0))
|
||||
}
|
||||
|
||||
// CompactRange compacts the underlying DB for the given key range.
|
||||
// In particular, deleted and overwritten versions are discarded,
|
||||
// and the data is rearranged to reduce the cost of operations
|
||||
// needed to access the data. This operation should typically only
|
||||
// be invoked by users who understand the underlying implementation.
|
||||
//
|
||||
// A nil Range.Start is treated as a key before all keys in the DB.
|
||||
// And a nil Range.Limit is treated as a key after all keys in the DB.
|
||||
// Therefore if both is nil then it will compact entire DB.
|
||||
func (db *DB) CompactRange(r util.Range) error {
|
||||
if err := db.ok(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case db.writeLockC <- struct{}{}:
|
||||
case _, _ = <-db.closeC:
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
// Check for overlaps in memdb.
|
||||
mem := db.getEffectiveMem()
|
||||
defer mem.decref()
|
||||
if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) {
|
||||
// Memdb compaction.
|
||||
if _, err := db.rotateMem(0); err != nil {
|
||||
<-db.writeLockC
|
||||
return err
|
||||
}
|
||||
<-db.writeLockC
|
||||
if err := db.compSendIdle(db.mcompCmdC); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
<-db.writeLockC
|
||||
}
|
||||
|
||||
// Table compaction.
|
||||
return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,16 @@
|
||||
// err = iter.Error()
|
||||
// ...
|
||||
//
|
||||
// Iterate over subset of database content with a particular prefix:
|
||||
// iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil)
|
||||
// for iter.Next() {
|
||||
// // Use key/value.
|
||||
// ...
|
||||
// }
|
||||
// iter.Release()
|
||||
// err = iter.Error()
|
||||
// ...
|
||||
//
|
||||
// Seek-then-Iterate:
|
||||
//
|
||||
// iter := db.NewIterator(nil, nil)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrSnapshotReleased = errors.New("leveldb: snapshot released")
|
||||
ErrIterReleased = errors.New("leveldb: iterator released")
|
||||
ErrClosed = errors.New("leveldb: closed")
|
||||
)
|
||||
|
||||
type CorruptionType int
|
||||
|
||||
const (
|
||||
CorruptedManifest CorruptionType = iota
|
||||
MissingFiles
|
||||
)
|
||||
|
||||
// ErrCorrupted is the type that wraps errors that indicate corruption in
|
||||
// the database.
|
||||
type ErrCorrupted struct {
|
||||
Type CorruptionType
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ErrCorrupted) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
+5
-6
@@ -21,7 +21,7 @@ var _ = testutil.Defer(func() {
|
||||
BlockRestartInterval: 5,
|
||||
BlockSize: 50,
|
||||
Compression: opt.NoCompression,
|
||||
MaxOpenFiles: 0,
|
||||
CachedOpenFiles: -1,
|
||||
Strict: opt.StrictAll,
|
||||
WriteBuffer: 1000,
|
||||
}
|
||||
@@ -36,22 +36,21 @@ var _ = testutil.Defer(func() {
|
||||
testutil.DoDBTesting(&t)
|
||||
db.TestClose()
|
||||
done <- true
|
||||
}, 9.0)
|
||||
}, 20.0)
|
||||
})
|
||||
|
||||
Describe("read test", func() {
|
||||
testutil.AllKeyValueTesting(nil, func(kv testutil.KeyValue) testutil.DB {
|
||||
testutil.AllKeyValueTesting(nil, nil, func(kv testutil.KeyValue) testutil.DB {
|
||||
// Building the DB.
|
||||
db := newTestingDB(o, nil, nil)
|
||||
kv.IterateShuffled(nil, func(i int, key, value []byte) {
|
||||
err := db.TestPut(key, value)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
testutil.Defer("teardown", func() {
|
||||
db.TestClose()
|
||||
})
|
||||
|
||||
return db
|
||||
}, func(db testutil.DB) {
|
||||
db.(*testingDB).TestClose()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build go1.3
|
||||
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDBReadConcurrent(b *testing.B) {
|
||||
p := openDBBench(b, false)
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.gc()
|
||||
defer p.close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(116)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
iter := p.newIter()
|
||||
defer iter.Release()
|
||||
for pb.Next() && iter.Next() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDBReadConcurrent2(b *testing.B) {
|
||||
p := openDBBench(b, false)
|
||||
p.populate(b.N)
|
||||
p.fill()
|
||||
p.gc()
|
||||
defer p.close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(116)
|
||||
|
||||
var dir uint32
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
iter := p.newIter()
|
||||
defer iter.Release()
|
||||
if atomic.AddUint32(&dir, 1)%2 == 0 {
|
||||
for pb.Next() && iter.Next() {
|
||||
}
|
||||
} else {
|
||||
if pb.Next() && iter.Last() {
|
||||
for pb.Next() && iter.Prev() {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+28
-2
@@ -40,13 +40,19 @@ type basicArrayIterator struct {
|
||||
util.BasicReleaser
|
||||
array BasicArray
|
||||
pos int
|
||||
err error
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Valid() bool {
|
||||
return i.pos >= 0 && i.pos < i.array.Len()
|
||||
return i.pos >= 0 && i.pos < i.array.Len() && !i.Released()
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) First() bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if i.array.Len() == 0 {
|
||||
i.pos = -1
|
||||
return false
|
||||
@@ -56,6 +62,11 @@ func (i *basicArrayIterator) First() bool {
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Last() bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
n := i.array.Len()
|
||||
if n == 0 {
|
||||
i.pos = 0
|
||||
@@ -66,6 +77,11 @@ func (i *basicArrayIterator) Last() bool {
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Seek(key []byte) bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
n := i.array.Len()
|
||||
if n == 0 {
|
||||
i.pos = 0
|
||||
@@ -79,6 +95,11 @@ func (i *basicArrayIterator) Seek(key []byte) bool {
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Next() bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
i.pos++
|
||||
if n := i.array.Len(); i.pos >= n {
|
||||
i.pos = n
|
||||
@@ -88,6 +109,11 @@ func (i *basicArrayIterator) Next() bool {
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Prev() bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
i.pos--
|
||||
if i.pos < 0 {
|
||||
i.pos = -1
|
||||
@@ -96,7 +122,7 @@ func (i *basicArrayIterator) Prev() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *basicArrayIterator) Error() error { return nil }
|
||||
func (i *basicArrayIterator) Error() error { return i.err }
|
||||
|
||||
type arrayIterator struct {
|
||||
basicArrayIterator
|
||||
|
||||
+33
-3
@@ -26,9 +26,10 @@ type indexedIterator struct {
|
||||
strict bool
|
||||
strictGet bool
|
||||
|
||||
data Iterator
|
||||
err error
|
||||
errf func(err error)
|
||||
data Iterator
|
||||
err error
|
||||
errf func(err error)
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (i *indexedIterator) setData() {
|
||||
@@ -50,6 +51,15 @@ func (i *indexedIterator) clearData() {
|
||||
i.data = nil
|
||||
}
|
||||
|
||||
func (i *indexedIterator) indexErr() {
|
||||
if err := i.index.Error(); err != nil {
|
||||
if i.errf != nil {
|
||||
i.errf(err)
|
||||
}
|
||||
i.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (i *indexedIterator) dataErr() bool {
|
||||
if i.errf != nil {
|
||||
if err := i.data.Error(); err != nil {
|
||||
@@ -72,9 +82,13 @@ func (i *indexedIterator) Valid() bool {
|
||||
func (i *indexedIterator) First() bool {
|
||||
if i.err != nil {
|
||||
return false
|
||||
} else if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.index.First() {
|
||||
i.indexErr()
|
||||
i.clearData()
|
||||
return false
|
||||
}
|
||||
@@ -85,9 +99,13 @@ func (i *indexedIterator) First() bool {
|
||||
func (i *indexedIterator) Last() bool {
|
||||
if i.err != nil {
|
||||
return false
|
||||
} else if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.index.Last() {
|
||||
i.indexErr()
|
||||
i.clearData()
|
||||
return false
|
||||
}
|
||||
@@ -105,9 +123,13 @@ func (i *indexedIterator) Last() bool {
|
||||
func (i *indexedIterator) Seek(key []byte) bool {
|
||||
if i.err != nil {
|
||||
return false
|
||||
} else if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.index.Seek(key) {
|
||||
i.indexErr()
|
||||
i.clearData()
|
||||
return false
|
||||
}
|
||||
@@ -125,6 +147,9 @@ func (i *indexedIterator) Seek(key []byte) bool {
|
||||
func (i *indexedIterator) Next() bool {
|
||||
if i.err != nil {
|
||||
return false
|
||||
} else if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -136,6 +161,7 @@ func (i *indexedIterator) Next() bool {
|
||||
fallthrough
|
||||
case i.data == nil:
|
||||
if !i.index.Next() {
|
||||
i.indexErr()
|
||||
return false
|
||||
}
|
||||
i.setData()
|
||||
@@ -147,6 +173,9 @@ func (i *indexedIterator) Next() bool {
|
||||
func (i *indexedIterator) Prev() bool {
|
||||
if i.err != nil {
|
||||
return false
|
||||
} else if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -158,6 +187,7 @@ func (i *indexedIterator) Prev() bool {
|
||||
fallthrough
|
||||
case i.data == nil:
|
||||
if !i.index.Prev() {
|
||||
i.indexErr()
|
||||
return false
|
||||
}
|
||||
i.setData()
|
||||
|
||||
+8
-19
@@ -14,6 +14,10 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIterReleased = errors.New("leveldb/iterator: iterator released")
|
||||
)
|
||||
|
||||
// IteratorSeeker is the interface that wraps the 'seeks method'.
|
||||
type IteratorSeeker interface {
|
||||
// First moves the iterator to the first key/value pair. If the iterator
|
||||
@@ -100,28 +104,13 @@ type ErrorCallbackSetter interface {
|
||||
}
|
||||
|
||||
type emptyIterator struct {
|
||||
releaser util.Releaser
|
||||
released bool
|
||||
err error
|
||||
util.BasicReleaser
|
||||
err error
|
||||
}
|
||||
|
||||
func (i *emptyIterator) rErr() {
|
||||
if i.err == nil && i.released {
|
||||
i.err = errors.New("leveldb/iterator: iterator released")
|
||||
}
|
||||
}
|
||||
|
||||
func (i *emptyIterator) Release() {
|
||||
if i.releaser != nil {
|
||||
i.releaser.Release()
|
||||
i.releaser = nil
|
||||
}
|
||||
i.released = true
|
||||
}
|
||||
|
||||
func (i *emptyIterator) SetReleaser(releaser util.Releaser) {
|
||||
if !i.released {
|
||||
i.releaser = releaser
|
||||
if i.err == nil && i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-8
@@ -7,16 +7,10 @@
|
||||
package iterator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIterReleased = errors.New("leveldb/iterator: iterator released")
|
||||
)
|
||||
|
||||
type dir int
|
||||
|
||||
const (
|
||||
@@ -274,9 +268,13 @@ func (i *mergedIterator) Release() {
|
||||
}
|
||||
|
||||
func (i *mergedIterator) SetReleaser(releaser util.Releaser) {
|
||||
if i.dir != dirReleased {
|
||||
i.releaser = releaser
|
||||
if i.dir == dirReleased {
|
||||
panic(util.ErrReleased)
|
||||
}
|
||||
if i.releaser != nil && releaser != nil {
|
||||
panic(util.ErrHasReleaser)
|
||||
}
|
||||
i.releaser = releaser
|
||||
}
|
||||
|
||||
func (i *mergedIterator) Error() error {
|
||||
|
||||
+64
-57
@@ -103,18 +103,18 @@ type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// DroppedError is the error type that passed to Dropper.Drop method.
|
||||
type DroppedError struct {
|
||||
// ErrCorrupted is the error type that generated by corrupted block or chunk.
|
||||
type ErrCorrupted struct {
|
||||
Size int
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e DroppedError) Error() string {
|
||||
return fmt.Sprintf("leveldb/journal: dropped %d bytes: %s", e.Size, e.Reason)
|
||||
func (e ErrCorrupted) Error() string {
|
||||
return fmt.Sprintf("leveldb/journal: block/chunk corrupted: %s (%d bytes)", e.Reason, e.Size)
|
||||
}
|
||||
|
||||
// Dropper is the interface that wrap simple Drop method. The Drop
|
||||
// method will be called when the journal reader dropping a chunk.
|
||||
// method will be called when the journal reader dropping a block or chunk.
|
||||
type Dropper interface {
|
||||
Drop(err error)
|
||||
}
|
||||
@@ -158,76 +158,78 @@ func NewReader(r io.Reader, dropper Dropper, strict, checksum bool) *Reader {
|
||||
}
|
||||
}
|
||||
|
||||
var errSkip = errors.New("leveldb/journal: skipped")
|
||||
|
||||
func (r *Reader) corrupt(n int, reason string, skip bool) error {
|
||||
if r.dropper != nil {
|
||||
r.dropper.Drop(ErrCorrupted{n, reason})
|
||||
}
|
||||
if r.strict && !skip {
|
||||
r.err = ErrCorrupted{n, reason}
|
||||
return r.err
|
||||
}
|
||||
return errSkip
|
||||
}
|
||||
|
||||
// nextChunk sets r.buf[r.i:r.j] to hold the next chunk's payload, reading the
|
||||
// next block into the buffer if necessary.
|
||||
func (r *Reader) nextChunk(wantFirst, skip bool) error {
|
||||
func (r *Reader) nextChunk(first bool) error {
|
||||
for {
|
||||
if r.j+headerSize <= r.n {
|
||||
checksum := binary.LittleEndian.Uint32(r.buf[r.j+0 : r.j+4])
|
||||
length := binary.LittleEndian.Uint16(r.buf[r.j+4 : r.j+6])
|
||||
chunkType := r.buf[r.j+6]
|
||||
|
||||
var err error
|
||||
if checksum == 0 && length == 0 && chunkType == 0 {
|
||||
// Drop entire block.
|
||||
err = DroppedError{r.n - r.j, "zero header"}
|
||||
m := r.n - r.j
|
||||
r.i = r.n
|
||||
r.j = r.n
|
||||
return r.corrupt(m, "zero header", false)
|
||||
} else {
|
||||
m := r.n - r.j
|
||||
r.i = r.j + headerSize
|
||||
r.j = r.j + headerSize + int(length)
|
||||
if r.j > r.n {
|
||||
// Drop entire block.
|
||||
err = DroppedError{m, "chunk length overflows block"}
|
||||
r.i = r.n
|
||||
r.j = r.n
|
||||
return r.corrupt(m, "chunk length overflows block", false)
|
||||
} else if r.checksum && checksum != util.NewCRC(r.buf[r.i-1:r.j]).Value() {
|
||||
// Drop entire block.
|
||||
err = DroppedError{m, "checksum mismatch"}
|
||||
r.i = r.n
|
||||
r.j = r.n
|
||||
return r.corrupt(m, "checksum mismatch", false)
|
||||
}
|
||||
}
|
||||
if wantFirst && err == nil && chunkType != fullChunkType && chunkType != firstChunkType {
|
||||
if skip {
|
||||
// The chunk are intentionally skipped.
|
||||
if chunkType == lastChunkType {
|
||||
skip = false
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
// Drop the chunk.
|
||||
err = DroppedError{r.j - r.i + headerSize, "orphan chunk"}
|
||||
}
|
||||
if first && chunkType != fullChunkType && chunkType != firstChunkType {
|
||||
m := r.j - r.i
|
||||
r.i = r.j
|
||||
// Report the error, but skip it.
|
||||
return r.corrupt(m+headerSize, "orphan chunk", true)
|
||||
}
|
||||
if err == nil {
|
||||
r.last = chunkType == fullChunkType || chunkType == lastChunkType
|
||||
} else {
|
||||
if r.dropper != nil {
|
||||
r.dropper.Drop(err)
|
||||
}
|
||||
if r.strict {
|
||||
r.err = err
|
||||
}
|
||||
r.last = chunkType == fullChunkType || chunkType == lastChunkType
|
||||
return nil
|
||||
}
|
||||
|
||||
// The last block.
|
||||
if r.n < blockSize && r.n > 0 {
|
||||
if !first {
|
||||
return r.corrupt(0, "missing chunk part", false)
|
||||
}
|
||||
r.err = io.EOF
|
||||
return r.err
|
||||
}
|
||||
|
||||
// Read block.
|
||||
n, err := io.ReadFull(r.r, r.buf[:])
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
if r.n < blockSize && r.n > 0 {
|
||||
// This is the last block.
|
||||
if r.j != r.n {
|
||||
r.err = io.ErrUnexpectedEOF
|
||||
} else {
|
||||
r.err = io.EOF
|
||||
}
|
||||
return r.err
|
||||
}
|
||||
n, err := io.ReadFull(r.r, r.buf[:])
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
r.err = err
|
||||
return r.err
|
||||
}
|
||||
if n == 0 {
|
||||
if !first {
|
||||
return r.corrupt(0, "missing chunk part", false)
|
||||
}
|
||||
r.err = io.EOF
|
||||
return r.err
|
||||
}
|
||||
@@ -237,29 +239,26 @@ func (r *Reader) nextChunk(wantFirst, skip bool) error {
|
||||
|
||||
// Next returns a reader for the next journal. It returns io.EOF if there are no
|
||||
// more journals. The reader returned becomes stale after the next Next call,
|
||||
// and should no longer be used.
|
||||
// and should no longer be used. If strict is false, the reader will returns
|
||||
// io.ErrUnexpectedEOF error when found corrupted journal.
|
||||
func (r *Reader) Next() (io.Reader, error) {
|
||||
r.seq++
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
skip := !r.last
|
||||
r.i = r.j
|
||||
for {
|
||||
r.i = r.j
|
||||
if r.nextChunk(true, skip) != nil {
|
||||
// So that 'orphan chunk' drop will be reported.
|
||||
skip = false
|
||||
} else {
|
||||
if err := r.nextChunk(true); err == nil {
|
||||
break
|
||||
}
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
} else if err != errSkip {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &singleReader{r, r.seq, nil}, nil
|
||||
}
|
||||
|
||||
// Reset resets the journal reader, allows reuse of the journal reader.
|
||||
// Reset resets the journal reader, allows reuse of the journal reader. Reset returns
|
||||
// last accumulated error.
|
||||
func (r *Reader) Reset(reader io.Reader, dropper Dropper, strict, checksum bool) error {
|
||||
r.seq++
|
||||
err := r.err
|
||||
@@ -296,7 +295,11 @@ func (x *singleReader) Read(p []byte) (int, error) {
|
||||
if r.last {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if x.err = r.nextChunk(false, false); x.err != nil {
|
||||
x.err = r.nextChunk(false)
|
||||
if x.err != nil {
|
||||
if x.err == errSkip {
|
||||
x.err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return 0, x.err
|
||||
}
|
||||
}
|
||||
@@ -320,7 +323,11 @@ func (x *singleReader) ReadByte() (byte, error) {
|
||||
if r.last {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if x.err = r.nextChunk(false, false); x.err != nil {
|
||||
x.err = r.nextChunk(false)
|
||||
if x.err != nil {
|
||||
if x.err == errSkip {
|
||||
x.err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return 0, x.err
|
||||
}
|
||||
}
|
||||
|
||||
+490
@@ -12,6 +12,7 @@ package journal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -326,3 +327,492 @@ func TestStaleWriter(t *testing.T) {
|
||||
t.Fatalf("stale write #1: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_MissingLastBlock(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-1024)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Cut the last block.
|
||||
b := buf.Bytes()[:blockSize]
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read.
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if n != blockSize-1024 {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, blockSize-1024)
|
||||
}
|
||||
|
||||
// Second read.
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("read #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_CorruptedFirstBlock(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
|
||||
t.Fatalf("write #2: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Fourth record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
|
||||
t.Fatalf("write #3: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
// Corrupting block #0.
|
||||
for i := 0; i < 1024; i++ {
|
||||
b[i] = '1'
|
||||
}
|
||||
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read (third record).
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if want := int64(blockSize-headerSize) + 1; n != want {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Second read (fourth record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #1: %v", err)
|
||||
}
|
||||
if want := int64(blockSize-headerSize) + 2; n != want {
|
||||
t.Fatalf("read #1: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_CorruptedMiddleBlock(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
|
||||
t.Fatalf("write #2: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Fourth record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
|
||||
t.Fatalf("write #3: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
// Corrupting block #1.
|
||||
for i := 0; i < 1024; i++ {
|
||||
b[blockSize+i] = '1'
|
||||
}
|
||||
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read (first record).
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if want := int64(blockSize / 2); n != want {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Second read (second record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("read #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third read (fourth record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #2: %v", err)
|
||||
}
|
||||
if want := int64(blockSize-headerSize) + 2; n != want {
|
||||
t.Fatalf("read #2: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_CorruptedLastBlock(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
|
||||
t.Fatalf("write #2: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Fourth record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
|
||||
t.Fatalf("write #3: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
// Corrupting block #3.
|
||||
for i := len(b) - 1; i > len(b)-1024; i-- {
|
||||
b[i] = '1'
|
||||
}
|
||||
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read (first record).
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if want := int64(blockSize / 2); n != want {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Second read (second record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #1: %v", err)
|
||||
}
|
||||
if want := int64(blockSize - headerSize); n != want {
|
||||
t.Fatalf("read #1: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Third read (third record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #2: %v", err)
|
||||
}
|
||||
if want := int64(blockSize-headerSize) + 1; n != want {
|
||||
t.Fatalf("read #2: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Fourth read (fourth record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("read #3: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_FirstChuckLengthOverflow(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
|
||||
t.Fatalf("write #2: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
// Corrupting record #1.
|
||||
x := blockSize
|
||||
binary.LittleEndian.PutUint16(b[x+4:], 0xffff)
|
||||
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read (first record).
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if want := int64(blockSize / 2); n != want {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Second read (second record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("read #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrupt_MiddleChuckLengthOverflow(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
w := NewWriter(buf)
|
||||
|
||||
// First record.
|
||||
ww, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
|
||||
t.Fatalf("write #0: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Second record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
|
||||
t.Fatalf("write #1: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Third record.
|
||||
ww, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
|
||||
t.Fatalf("write #2: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := buf.Bytes()
|
||||
// Corrupting record #1.
|
||||
x := blockSize/2 + headerSize
|
||||
binary.LittleEndian.PutUint16(b[x+4:], 0xffff)
|
||||
|
||||
r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
|
||||
|
||||
// First read (first record).
|
||||
rr, err := r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #0: %v", err)
|
||||
}
|
||||
if want := int64(blockSize / 2); n != want {
|
||||
t.Fatalf("read #0: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
// Second read (third record).
|
||||
rr, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err = io.Copy(ioutil.Discard, rr)
|
||||
if err != nil {
|
||||
t.Fatalf("read #1: %v", err)
|
||||
}
|
||||
if want := int64(blockSize-headerSize) + 1; n != want {
|
||||
t.Fatalf("read #1: got %d bytes want %d", n, want)
|
||||
}
|
||||
|
||||
if _, err := r.Next(); err != io.EOF {
|
||||
t.Fatalf("last next: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
)
|
||||
|
||||
var icmp = &iComparer{comparer.DefaultComparer}
|
||||
var defaultIComparer = &iComparer{comparer.DefaultComparer}
|
||||
|
||||
func ikey(key string, seq uint64, t vType) iKey {
|
||||
return newIKey([]byte(key), uint64(seq), t)
|
||||
@@ -21,7 +21,7 @@ func ikey(key string, seq uint64, t vType) iKey {
|
||||
|
||||
func shortSep(a, b []byte) []byte {
|
||||
dst := make([]byte, len(a))
|
||||
dst = icmp.Separator(dst[:0], a, b)
|
||||
dst = defaultIComparer.Separator(dst[:0], a, b)
|
||||
if dst == nil {
|
||||
return a
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func shortSep(a, b []byte) []byte {
|
||||
|
||||
func shortSuccessor(b []byte) []byte {
|
||||
dst := make([]byte, len(b))
|
||||
dst = icmp.Successor(dst[:0], b)
|
||||
dst = defaultIComparer.Successor(dst[:0], b)
|
||||
if dst == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
+25
-7
@@ -8,6 +8,7 @@
|
||||
package memdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"sync"
|
||||
|
||||
@@ -17,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrIterReleased = errors.New("leveldb/memdb: iterator released")
|
||||
)
|
||||
|
||||
const tMaxHeight = 12
|
||||
@@ -29,6 +31,7 @@ type dbIter struct {
|
||||
node int
|
||||
forward bool
|
||||
key, value []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (i *dbIter) fill(checkStart, checkLimit bool) bool {
|
||||
@@ -59,6 +62,11 @@ func (i *dbIter) Valid() bool {
|
||||
}
|
||||
|
||||
func (i *dbIter) First() bool {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
i.forward = true
|
||||
i.p.mu.RLock()
|
||||
defer i.p.mu.RUnlock()
|
||||
@@ -71,9 +79,11 @@ func (i *dbIter) First() bool {
|
||||
}
|
||||
|
||||
func (i *dbIter) Last() bool {
|
||||
if i.p == nil {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
i.forward = false
|
||||
i.p.mu.RLock()
|
||||
defer i.p.mu.RUnlock()
|
||||
@@ -86,9 +96,11 @@ func (i *dbIter) Last() bool {
|
||||
}
|
||||
|
||||
func (i *dbIter) Seek(key []byte) bool {
|
||||
if i.p == nil {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
i.forward = true
|
||||
i.p.mu.RLock()
|
||||
defer i.p.mu.RUnlock()
|
||||
@@ -100,9 +112,11 @@ func (i *dbIter) Seek(key []byte) bool {
|
||||
}
|
||||
|
||||
func (i *dbIter) Next() bool {
|
||||
if i.p == nil {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if i.node == 0 {
|
||||
if !i.forward {
|
||||
return i.First()
|
||||
@@ -117,9 +131,11 @@ func (i *dbIter) Next() bool {
|
||||
}
|
||||
|
||||
func (i *dbIter) Prev() bool {
|
||||
if i.p == nil {
|
||||
if i.Released() {
|
||||
i.err = ErrIterReleased
|
||||
return false
|
||||
}
|
||||
|
||||
if i.node == 0 {
|
||||
if i.forward {
|
||||
return i.Last()
|
||||
@@ -141,10 +157,10 @@ func (i *dbIter) Value() []byte {
|
||||
return i.value
|
||||
}
|
||||
|
||||
func (i *dbIter) Error() error { return nil }
|
||||
func (i *dbIter) Error() error { return i.err }
|
||||
|
||||
func (i *dbIter) Release() {
|
||||
if i.p != nil {
|
||||
if !i.Released() {
|
||||
i.p = nil
|
||||
i.node = 0
|
||||
i.key = nil
|
||||
@@ -437,6 +453,8 @@ func (p *DB) Reset() {
|
||||
// New creates a new initalized in-memory key/value DB. The capacity
|
||||
// is the initial key/value buffer capacity. The capacity is advisory,
|
||||
// not enforced.
|
||||
//
|
||||
// The returned DB instance is goroutine-safe.
|
||||
func New(cmp comparer.BasicComparer, capacity int) *DB {
|
||||
p := &DB{
|
||||
cmp: cmp,
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ var _ = testutil.Defer(func() {
|
||||
}
|
||||
|
||||
return db
|
||||
})
|
||||
}, nil, nil)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+27
-19
@@ -24,16 +24,22 @@ const (
|
||||
DefaultBlockRestartInterval = 16
|
||||
DefaultBlockSize = 4 * KiB
|
||||
DefaultCompressionType = SnappyCompression
|
||||
DefaultMaxOpenFiles = 1000
|
||||
DefaultCachedOpenFiles = 500
|
||||
DefaultWriteBuffer = 4 * MiB
|
||||
)
|
||||
|
||||
type noCache struct{}
|
||||
|
||||
func (noCache) SetCapacity(capacity int) {}
|
||||
func (noCache) GetNamespace(id uint64) cache.Namespace { return nil }
|
||||
func (noCache) Purge(fin cache.PurgeFin) {}
|
||||
func (noCache) Zap(closed bool) {}
|
||||
func (noCache) SetCapacity(capacity int) {}
|
||||
func (noCache) Capacity() int { return 0 }
|
||||
func (noCache) Used() int { return 0 }
|
||||
func (noCache) Size() int { return 0 }
|
||||
func (noCache) NumObjects() int { return 0 }
|
||||
func (noCache) GetNamespace(id uint64) cache.Namespace { return nil }
|
||||
func (noCache) PurgeNamespace(id uint64, fin cache.PurgeFin) {}
|
||||
func (noCache) ZapNamespace(id uint64) {}
|
||||
func (noCache) Purge(fin cache.PurgeFin) {}
|
||||
func (noCache) Zap() {}
|
||||
|
||||
var NoCache cache.Cache = noCache{}
|
||||
|
||||
@@ -119,6 +125,13 @@ type Options struct {
|
||||
// The default value is 4KiB.
|
||||
BlockSize int
|
||||
|
||||
// CachedOpenFiles defines number of open files to kept around when not
|
||||
// in-use, the counting includes still in-use files.
|
||||
// Set this to negative value to disable caching.
|
||||
//
|
||||
// The default value is 500.
|
||||
CachedOpenFiles int
|
||||
|
||||
// Comparer defines a total ordering over the space of []byte keys: a 'less
|
||||
// than' relationship. The same comparison algorithm must be used for reads
|
||||
// and writes over the lifetime of the DB.
|
||||
@@ -159,13 +172,6 @@ type Options struct {
|
||||
// The default value is nil.
|
||||
Filter filter.Filter
|
||||
|
||||
// MaxOpenFiles defines maximum number of open files to kept around
|
||||
// (cached). This is not an hard limit, actual open files may exceed
|
||||
// the defined value.
|
||||
//
|
||||
// The default value is 1000.
|
||||
MaxOpenFiles int
|
||||
|
||||
// Strict defines the DB strict level.
|
||||
Strict Strict
|
||||
|
||||
@@ -207,6 +213,15 @@ func (o *Options) GetBlockSize() int {
|
||||
return o.BlockSize
|
||||
}
|
||||
|
||||
func (o *Options) GetCachedOpenFiles() int {
|
||||
if o == nil || o.CachedOpenFiles == 0 {
|
||||
return DefaultCachedOpenFiles
|
||||
} else if o.CachedOpenFiles < 0 {
|
||||
return 0
|
||||
}
|
||||
return o.CachedOpenFiles
|
||||
}
|
||||
|
||||
func (o *Options) GetComparer() comparer.Comparer {
|
||||
if o == nil || o.Comparer == nil {
|
||||
return comparer.DefaultComparer
|
||||
@@ -242,13 +257,6 @@ func (o *Options) GetFilter() filter.Filter {
|
||||
return o.Filter
|
||||
}
|
||||
|
||||
func (o *Options) GetMaxOpenFiles() int {
|
||||
if o == nil || o.MaxOpenFiles <= 0 {
|
||||
return DefaultMaxOpenFiles
|
||||
}
|
||||
return o.MaxOpenFiles
|
||||
}
|
||||
|
||||
func (o *Options) GetStrict(strict Strict) bool {
|
||||
if o == nil || o.Strict == 0 {
|
||||
return DefaultStrict&strict != 0
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ func (s *session) setOptions(o *opt.Options) {
|
||||
s.o.BlockCache = nil
|
||||
}
|
||||
// Comparer.
|
||||
s.cmp = &iComparer{o.GetComparer()}
|
||||
s.o.Comparer = s.cmp
|
||||
s.icmp = &iComparer{o.GetComparer()}
|
||||
s.o.Comparer = s.icmp
|
||||
// Filter.
|
||||
if filter := o.GetFilter(); filter != nil {
|
||||
s.o.Filter = &iFilter{filter}
|
||||
|
||||
+85
-92
@@ -20,16 +20,6 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// ErrManifest is the type that wraps errors produced by missing
|
||||
// or corrupted manifest file.
|
||||
type ErrManifest struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ErrManifest) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// session represent a persistent database session.
|
||||
type session struct {
|
||||
// Need 64-bit alignment.
|
||||
@@ -42,18 +32,19 @@ type session struct {
|
||||
stor storage.Storage
|
||||
storLock util.Releaser
|
||||
o *opt.Options
|
||||
cmp *iComparer
|
||||
icmp *iComparer
|
||||
tops *tOps
|
||||
|
||||
manifest *journal.Writer
|
||||
manifestWriter storage.Writer
|
||||
manifestFile storage.File
|
||||
|
||||
stCPtrs [kNumLevels]iKey // compact pointers; need external synchronization
|
||||
stCptrs [kNumLevels]iKey // compact pointers; need external synchronization
|
||||
stVersion *version // current version
|
||||
vmu sync.Mutex
|
||||
}
|
||||
|
||||
// Creates new initialized session instance.
|
||||
func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
|
||||
if stor == nil {
|
||||
return nil, os.ErrInvalid
|
||||
@@ -67,7 +58,7 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
|
||||
storLock: storLock,
|
||||
}
|
||||
s.setOptions(o)
|
||||
s.tops = newTableOps(s, s.o.GetMaxOpenFiles())
|
||||
s.tops = newTableOps(s, s.o.GetCachedOpenFiles())
|
||||
s.setVersion(&version{s: s})
|
||||
s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock D·DeletedEntry L·Level Q·SeqNum T·TimeElapsed")
|
||||
return
|
||||
@@ -91,6 +82,7 @@ func (s *session) close() {
|
||||
s.stVersion = nil
|
||||
}
|
||||
|
||||
// Release session lock.
|
||||
func (s *session) release() {
|
||||
s.storLock.Release()
|
||||
}
|
||||
@@ -108,7 +100,7 @@ func (s *session) recover() (err error) {
|
||||
// Don't return os.ErrNotExist if the underlying storage contains
|
||||
// other files that belong to LevelDB. So the DB won't get trashed.
|
||||
if files, _ := s.stor.GetFiles(storage.TypeAll); len(files) > 0 {
|
||||
err = ErrManifest{Err: errors.New("leveldb: manifest file missing")}
|
||||
err = ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest file missing")}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -142,13 +134,13 @@ func (s *session) recover() (err error) {
|
||||
err = rec.decode(r)
|
||||
if err == nil {
|
||||
// save compact pointers
|
||||
for _, rp := range rec.compactionPointers {
|
||||
s.stCPtrs[rp.level] = iKey(rp.key)
|
||||
for _, r := range rec.compactionPointers {
|
||||
s.stCptrs[r.level] = iKey(r.ikey)
|
||||
}
|
||||
// commit record to version staging
|
||||
staging.commit(rec)
|
||||
} else if strict {
|
||||
return ErrManifest{Err: err}
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: err}
|
||||
} else {
|
||||
s.logf("manifest error: %v (skipped)", err)
|
||||
}
|
||||
@@ -159,15 +151,15 @@ func (s *session) recover() (err error) {
|
||||
|
||||
switch {
|
||||
case !rec.has(recComparer):
|
||||
return ErrManifest{Err: errors.New("leveldb: manifest missing comparer name")}
|
||||
case rec.comparer != s.cmp.cmp.Name():
|
||||
return ErrManifest{Err: errors.New("leveldb: comparer mismatch, " + "want '" + s.cmp.cmp.Name() + "', " + "got '" + rec.comparer + "'")}
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing comparer name")}
|
||||
case rec.comparer != s.icmp.uName():
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: comparer mismatch, " + "want '" + s.icmp.uName() + "', " + "got '" + rec.comparer + "'")}
|
||||
case !rec.has(recNextNum):
|
||||
return ErrManifest{Err: errors.New("leveldb: manifest missing next file number")}
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing next file number")}
|
||||
case !rec.has(recJournalNum):
|
||||
return ErrManifest{Err: errors.New("leveldb: manifest missing journal file number")}
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing journal file number")}
|
||||
case !rec.has(recSeq):
|
||||
return ErrManifest{Err: errors.New("leveldb: manifest missing seq number")}
|
||||
return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing seq number")}
|
||||
}
|
||||
|
||||
s.manifestFile = file
|
||||
@@ -199,25 +191,22 @@ func (s *session) commit(r *sessionRecord) (err error) {
|
||||
|
||||
// Pick a compaction based on current state; need external synchronization.
|
||||
func (s *session) pickCompaction() *compaction {
|
||||
icmp := s.cmp
|
||||
ucmp := icmp.cmp
|
||||
|
||||
v := s.version_NB()
|
||||
|
||||
var level int
|
||||
var t0 tFiles
|
||||
if v.cScore >= 1 {
|
||||
level = v.cLevel
|
||||
cp := s.stCPtrs[level]
|
||||
tt := v.tables[level]
|
||||
for _, t := range tt {
|
||||
if cp == nil || icmp.Compare(t.max, cp) > 0 {
|
||||
cptr := s.stCptrs[level]
|
||||
tables := v.tables[level]
|
||||
for _, t := range tables {
|
||||
if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
|
||||
t0 = append(t0, t)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(t0) == 0 {
|
||||
t0 = append(t0, tt[0])
|
||||
t0 = append(t0, tables[0])
|
||||
}
|
||||
} else {
|
||||
if p := atomic.LoadPointer(&v.cSeek); p != nil {
|
||||
@@ -229,11 +218,10 @@ func (s *session) pickCompaction() *compaction {
|
||||
}
|
||||
}
|
||||
|
||||
c := &compaction{s: s, version: v, level: level}
|
||||
c := &compaction{s: s, v: v, level: level}
|
||||
if level == 0 {
|
||||
min, max := t0.getRange(icmp)
|
||||
t0 = nil
|
||||
v.tables[0].getOverlaps(min.ukey(), max.ukey(), &t0, false, ucmp)
|
||||
imin, imax := t0.getRange(s.icmp)
|
||||
t0 = v.tables[0].getOverlaps(t0[:0], s.icmp, imin.ukey(), imax.ukey(), true)
|
||||
}
|
||||
|
||||
c.tables[0] = t0
|
||||
@@ -242,25 +230,41 @@ func (s *session) pickCompaction() *compaction {
|
||||
}
|
||||
|
||||
// Create compaction from given level and range; need external synchronization.
|
||||
func (s *session) getCompactionRange(level int, min, max []byte) *compaction {
|
||||
func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
|
||||
v := s.version_NB()
|
||||
|
||||
var t0 tFiles
|
||||
v.tables[level].getOverlaps(min, max, &t0, level != 0, s.cmp.cmp)
|
||||
t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
|
||||
if len(t0) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &compaction{s: s, version: v, level: level}
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if level > 0 {
|
||||
limit := uint64(kMaxTableSize)
|
||||
total := uint64(0)
|
||||
for i, t := range t0 {
|
||||
total += t.size
|
||||
if total >= limit {
|
||||
s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
|
||||
t0 = t0[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c := &compaction{s: s, v: v, level: level}
|
||||
c.tables[0] = t0
|
||||
c.expand()
|
||||
return c
|
||||
}
|
||||
|
||||
// compaction represent a compaction state
|
||||
// compaction represent a compaction state.
|
||||
type compaction struct {
|
||||
s *session
|
||||
version *version
|
||||
s *session
|
||||
v *version
|
||||
|
||||
level int
|
||||
tables [2]tFiles
|
||||
@@ -269,44 +273,36 @@ type compaction struct {
|
||||
gpidx int
|
||||
seenKey bool
|
||||
overlappedBytes uint64
|
||||
min, max iKey
|
||||
imin, imax iKey
|
||||
|
||||
tPtrs [kNumLevels]int
|
||||
}
|
||||
|
||||
// Expand compacted tables; need external synchronization.
|
||||
func (c *compaction) expand() {
|
||||
s := c.s
|
||||
v := c.version
|
||||
icmp := s.cmp
|
||||
ucmp := icmp.cmp
|
||||
|
||||
level := c.level
|
||||
vt0, vt1 := v.tables[level], v.tables[level+1]
|
||||
vt0, vt1 := c.v.tables[level], c.v.tables[level+1]
|
||||
|
||||
t0, t1 := c.tables[0], c.tables[1]
|
||||
min, max := t0.getRange(icmp)
|
||||
vt1.getOverlaps(min.ukey(), max.ukey(), &t1, true, ucmp)
|
||||
|
||||
// Get entire range covered by compaction
|
||||
amin, amax := append(t0, t1...).getRange(icmp)
|
||||
imin, imax := t0.getRange(c.s.icmp)
|
||||
t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
|
||||
// Get entire range covered by compaction.
|
||||
amin, amax := append(t0, t1...).getRange(c.s.icmp)
|
||||
|
||||
// See if we can grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up.
|
||||
if len(t1) > 0 {
|
||||
var exp0 tFiles
|
||||
vt0.getOverlaps(amin.ukey(), amax.ukey(), &exp0, level != 0, ucmp)
|
||||
exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), level == 0)
|
||||
if len(exp0) > len(t0) && t1.size()+exp0.size() < kExpCompactionMaxBytes {
|
||||
var exp1 tFiles
|
||||
xmin, xmax := exp0.getRange(icmp)
|
||||
vt1.getOverlaps(xmin.ukey(), xmax.ukey(), &exp1, true, ucmp)
|
||||
xmin, xmax := exp0.getRange(c.s.icmp)
|
||||
exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
|
||||
if len(exp1) == len(t1) {
|
||||
s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
|
||||
c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
|
||||
level, level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
|
||||
len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
|
||||
min, max = xmin, xmax
|
||||
imin, imax = xmin, xmax
|
||||
t0, t1 = exp0, exp1
|
||||
amin, amax = append(t0, t1...).getRange(icmp)
|
||||
amin, amax = append(t0, t1...).getRange(c.s.icmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,11 +310,11 @@ func (c *compaction) expand() {
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2)
|
||||
if level+2 < kNumLevels {
|
||||
v.tables[level+2].getOverlaps(amin.ukey(), amax.ukey(), &c.gp, true, ucmp)
|
||||
c.gp = c.v.tables[level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
|
||||
}
|
||||
|
||||
c.tables[0], c.tables[1] = t0, t1
|
||||
c.min, c.max = min, max
|
||||
c.imin, c.imax = imin, imax
|
||||
}
|
||||
|
||||
// Check whether compaction is trivial.
|
||||
@@ -326,17 +322,14 @@ func (c *compaction) trivial() bool {
|
||||
return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= kMaxGrandParentOverlapBytes
|
||||
}
|
||||
|
||||
func (c *compaction) isBaseLevelForKey(key []byte) bool {
|
||||
s := c.s
|
||||
v := c.version
|
||||
ucmp := s.cmp.cmp
|
||||
for level, tt := range v.tables[c.level+2:] {
|
||||
for c.tPtrs[level] < len(tt) {
|
||||
t := tt[c.tPtrs[level]]
|
||||
if ucmp.Compare(key, t.max.ukey()) <= 0 {
|
||||
// We've advanced far enough
|
||||
if ucmp.Compare(key, t.min.ukey()) >= 0 {
|
||||
// Key falls in this file's range, so definitely not base level
|
||||
func (c *compaction) baseLevelForKey(ukey []byte) bool {
|
||||
for level, tables := range c.v.tables[c.level+2:] {
|
||||
for c.tPtrs[level] < len(tables) {
|
||||
t := tables[c.tPtrs[level]]
|
||||
if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
|
||||
// We've advanced far enough.
|
||||
if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
|
||||
// Key falls in this file's range, so definitely not base level.
|
||||
return false
|
||||
}
|
||||
break
|
||||
@@ -347,11 +340,10 @@ func (c *compaction) isBaseLevelForKey(key []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *compaction) shouldStopBefore(key iKey) bool {
|
||||
icmp := c.s.cmp
|
||||
func (c *compaction) shouldStopBefore(ikey iKey) bool {
|
||||
for ; c.gpidx < len(c.gp); c.gpidx++ {
|
||||
gp := c.gp[c.gpidx]
|
||||
if icmp.Compare(key, gp.max) <= 0 {
|
||||
if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
|
||||
break
|
||||
}
|
||||
if c.seenKey {
|
||||
@@ -361,43 +353,44 @@ func (c *compaction) shouldStopBefore(key iKey) bool {
|
||||
c.seenKey = true
|
||||
|
||||
if c.overlappedBytes > kMaxGrandParentOverlapBytes {
|
||||
// Too much overlap for current output; start new output
|
||||
// Too much overlap for current output; start new output.
|
||||
c.overlappedBytes = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Creates an iterator.
|
||||
func (c *compaction) newIterator() iterator.Iterator {
|
||||
s := c.s
|
||||
icmp := s.cmp
|
||||
|
||||
level := c.level
|
||||
icap := 2
|
||||
// Creates iterator slice.
|
||||
icap := len(c.tables)
|
||||
if c.level == 0 {
|
||||
// Special case for level-0
|
||||
icap = len(c.tables[0]) + 1
|
||||
}
|
||||
its := make([]iterator.Iterator, 0, icap)
|
||||
|
||||
// Options.
|
||||
ro := &opt.ReadOptions{
|
||||
DontFillCache: true,
|
||||
}
|
||||
strict := s.o.GetStrict(opt.StrictIterator)
|
||||
strict := c.s.o.GetStrict(opt.StrictIterator)
|
||||
|
||||
for i, tt := range c.tables {
|
||||
if len(tt) == 0 {
|
||||
for i, tables := range c.tables {
|
||||
if len(tables) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if level+i == 0 {
|
||||
for _, t := range tt {
|
||||
its = append(its, s.tops.newIterator(t, nil, ro))
|
||||
// Level-0 is not sorted and may overlaps each other.
|
||||
if c.level+i == 0 {
|
||||
for _, t := range tables {
|
||||
its = append(its, c.s.tops.newIterator(t, nil, ro))
|
||||
}
|
||||
} else {
|
||||
it := iterator.NewIndexedIterator(tt.newIndexIterator(s.tops, icmp, nil, ro), strict, true)
|
||||
it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict, true)
|
||||
its = append(its, it)
|
||||
}
|
||||
}
|
||||
|
||||
return iterator.NewMergedIterator(its, icmp, true)
|
||||
return iterator.NewMergedIterator(its, c.s.icmp, true)
|
||||
}
|
||||
|
||||
+26
-26
@@ -35,19 +35,19 @@ const (
|
||||
|
||||
type cpRecord struct {
|
||||
level int
|
||||
key iKey
|
||||
ikey iKey
|
||||
}
|
||||
|
||||
type ntRecord struct {
|
||||
level int
|
||||
num uint64
|
||||
size uint64
|
||||
min iKey
|
||||
max iKey
|
||||
imin iKey
|
||||
imax iKey
|
||||
}
|
||||
|
||||
func (r ntRecord) makeFile(s *session) *tFile {
|
||||
return newTFile(s.getTableFile(r.num), r.size, r.min, r.max)
|
||||
return newTableFile(s.getTableFile(r.num), r.size, r.imin, r.imax)
|
||||
}
|
||||
|
||||
type dtRecord struct {
|
||||
@@ -98,9 +98,9 @@ func (p *sessionRecord) setSeq(seq uint64) {
|
||||
p.seq = seq
|
||||
}
|
||||
|
||||
func (p *sessionRecord) addCompactionPointer(level int, key iKey) {
|
||||
func (p *sessionRecord) addCompactionPointer(level int, ikey iKey) {
|
||||
p.hasRec |= 1 << recCompactionPointer
|
||||
p.compactionPointers = append(p.compactionPointers, cpRecord{level, key})
|
||||
p.compactionPointers = append(p.compactionPointers, cpRecord{level, ikey})
|
||||
}
|
||||
|
||||
func (p *sessionRecord) resetCompactionPointers() {
|
||||
@@ -108,13 +108,13 @@ func (p *sessionRecord) resetCompactionPointers() {
|
||||
p.compactionPointers = p.compactionPointers[:0]
|
||||
}
|
||||
|
||||
func (p *sessionRecord) addTable(level int, num, size uint64, min, max iKey) {
|
||||
func (p *sessionRecord) addTable(level int, num, size uint64, imin, imax iKey) {
|
||||
p.hasRec |= 1 << recNewTable
|
||||
p.addedTables = append(p.addedTables, ntRecord{level, num, size, min, max})
|
||||
p.addedTables = append(p.addedTables, ntRecord{level, num, size, imin, imax})
|
||||
}
|
||||
|
||||
func (p *sessionRecord) addTableFile(level int, t *tFile) {
|
||||
p.addTable(level, t.file.Num(), t.size, t.min, t.max)
|
||||
p.addTable(level, t.file.Num(), t.size, t.imin, t.imax)
|
||||
}
|
||||
|
||||
func (p *sessionRecord) resetAddedTables() {
|
||||
@@ -169,23 +169,23 @@ func (p *sessionRecord) encode(w io.Writer) error {
|
||||
p.putUvarint(w, recSeq)
|
||||
p.putUvarint(w, p.seq)
|
||||
}
|
||||
for _, cp := range p.compactionPointers {
|
||||
for _, r := range p.compactionPointers {
|
||||
p.putUvarint(w, recCompactionPointer)
|
||||
p.putUvarint(w, uint64(cp.level))
|
||||
p.putBytes(w, cp.key)
|
||||
p.putUvarint(w, uint64(r.level))
|
||||
p.putBytes(w, r.ikey)
|
||||
}
|
||||
for _, t := range p.deletedTables {
|
||||
for _, r := range p.deletedTables {
|
||||
p.putUvarint(w, recDeletedTable)
|
||||
p.putUvarint(w, uint64(t.level))
|
||||
p.putUvarint(w, t.num)
|
||||
p.putUvarint(w, uint64(r.level))
|
||||
p.putUvarint(w, r.num)
|
||||
}
|
||||
for _, t := range p.addedTables {
|
||||
for _, r := range p.addedTables {
|
||||
p.putUvarint(w, recNewTable)
|
||||
p.putUvarint(w, uint64(t.level))
|
||||
p.putUvarint(w, t.num)
|
||||
p.putUvarint(w, t.size)
|
||||
p.putBytes(w, t.min)
|
||||
p.putBytes(w, t.max)
|
||||
p.putUvarint(w, uint64(r.level))
|
||||
p.putUvarint(w, r.num)
|
||||
p.putUvarint(w, r.size)
|
||||
p.putBytes(w, r.imin)
|
||||
p.putBytes(w, r.imax)
|
||||
}
|
||||
return p.err
|
||||
}
|
||||
@@ -282,18 +282,18 @@ func (p *sessionRecord) decode(r io.Reader) error {
|
||||
}
|
||||
case recCompactionPointer:
|
||||
level := p.readLevel(br)
|
||||
key := p.readBytes(br)
|
||||
ikey := p.readBytes(br)
|
||||
if p.err == nil {
|
||||
p.addCompactionPointer(level, iKey(key))
|
||||
p.addCompactionPointer(level, iKey(ikey))
|
||||
}
|
||||
case recNewTable:
|
||||
level := p.readLevel(br)
|
||||
num := p.readUvarint(br)
|
||||
size := p.readUvarint(br)
|
||||
min := p.readBytes(br)
|
||||
max := p.readBytes(br)
|
||||
imin := p.readBytes(br)
|
||||
imax := p.readBytes(br)
|
||||
if p.err == nil {
|
||||
p.addTable(level, num, size, min, max)
|
||||
p.addTable(level, num, size, imin, imax)
|
||||
}
|
||||
case recDeletedTable:
|
||||
level := p.readLevel(br)
|
||||
|
||||
+11
-16
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
)
|
||||
|
||||
// logging
|
||||
// Logging.
|
||||
|
||||
type dropper struct {
|
||||
s *session
|
||||
@@ -22,22 +22,17 @@ type dropper struct {
|
||||
}
|
||||
|
||||
func (d dropper) Drop(err error) {
|
||||
if e, ok := err.(journal.DroppedError); ok {
|
||||
if e, ok := err.(journal.ErrCorrupted); ok {
|
||||
d.s.logf("journal@drop %s-%d S·%s %q", d.file.Type(), d.file.Num(), shortenb(e.Size), e.Reason)
|
||||
} else {
|
||||
d.s.logf("journal@drop %s-%d %q", d.file.Type(), d.file.Num(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) log(v ...interface{}) {
|
||||
s.stor.Log(fmt.Sprint(v...))
|
||||
}
|
||||
func (s *session) log(v ...interface{}) { s.stor.Log(fmt.Sprint(v...)) }
|
||||
func (s *session) logf(format string, v ...interface{}) { s.stor.Log(fmt.Sprintf(format, v...)) }
|
||||
|
||||
func (s *session) logf(format string, v ...interface{}) {
|
||||
s.stor.Log(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// file utils
|
||||
// File utils.
|
||||
|
||||
func (s *session) getJournalFile(num uint64) storage.File {
|
||||
return s.stor.GetFile(num, storage.TypeJournal)
|
||||
@@ -56,7 +51,7 @@ func (s *session) newTemp() storage.File {
|
||||
return s.stor.GetFile(num, storage.TypeTemp)
|
||||
}
|
||||
|
||||
// session state
|
||||
// Session state.
|
||||
|
||||
// Get current version.
|
||||
func (s *session) version() *version {
|
||||
@@ -126,7 +121,7 @@ func (s *session) reuseFileNum(num uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
// manifest related utils
|
||||
// Manifest related utils.
|
||||
|
||||
// Fill given session record obj with current states; need external
|
||||
// synchronization.
|
||||
@@ -142,17 +137,17 @@ func (s *session) fillRecord(r *sessionRecord, snapshot bool) {
|
||||
r.setSeq(s.stSeq)
|
||||
}
|
||||
|
||||
for level, ik := range s.stCPtrs {
|
||||
for level, ik := range s.stCptrs {
|
||||
if ik != nil {
|
||||
r.addCompactionPointer(level, ik)
|
||||
}
|
||||
}
|
||||
|
||||
r.setComparer(s.cmp.cmp.Name())
|
||||
r.setComparer(s.icmp.uName())
|
||||
}
|
||||
}
|
||||
|
||||
// Mark if record has been commited, this will update session state;
|
||||
// Mark if record has been committed, this will update session state;
|
||||
// need external synchronization.
|
||||
func (s *session) recordCommited(r *sessionRecord) {
|
||||
if r.has(recJournalNum) {
|
||||
@@ -168,7 +163,7 @@ func (s *session) recordCommited(r *sessionRecord) {
|
||||
}
|
||||
|
||||
for _, p := range r.compactionPointers {
|
||||
s.stCPtrs[p.level] = iKey(p.key)
|
||||
s.stCptrs[p.level] = iKey(p.ikey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -343,6 +343,20 @@ type fileWrap struct {
|
||||
f *file
|
||||
}
|
||||
|
||||
func (fw fileWrap) Sync() error {
|
||||
if err := fw.File.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if fw.f.Type() == TypeManifest {
|
||||
// Also sync parent directory if file type is manifest.
|
||||
// See: https://code.google.com/p/leveldb/issues/detail?id=190.
|
||||
if err := syncDir(fw.f.fs.path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw fileWrap) Close() error {
|
||||
f := fw.f
|
||||
f.fs.mu.Lock()
|
||||
|
||||
+12
@@ -38,3 +38,15 @@ func rename(oldpath, newpath string) error {
|
||||
_, fname := filepath.Split(newpath)
|
||||
return os.Rename(oldpath, fname)
|
||||
}
|
||||
|
||||
func syncDir(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type unixFileLock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (fl *unixFileLock) release() error {
|
||||
if err := setFileLock(fl.f, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return fl.f.Close()
|
||||
}
|
||||
|
||||
func newFileLock(path string) (fl fileLock, err error) {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = setFileLock(f, true)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
fl = &unixFileLock{f: f}
|
||||
return
|
||||
}
|
||||
|
||||
func setFileLock(f *os.File, lock bool) error {
|
||||
flock := syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
Whence: 1,
|
||||
}
|
||||
if lock {
|
||||
flock.Type = syscall.F_WRLCK
|
||||
}
|
||||
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &flock)
|
||||
}
|
||||
|
||||
func rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func syncDir(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+13
-1
@@ -4,7 +4,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin freebsd linux netbsd openbsd
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package storage
|
||||
|
||||
@@ -49,3 +49,15 @@ func setFileLock(f *os.File, lock bool) error {
|
||||
func rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func syncDir(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
@@ -65,3 +65,5 @@ func rename(oldpath, newpath string) error {
|
||||
}
|
||||
return moveFileEx(from, to, _MOVEFILE_REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
func syncDir(name string) error { return nil }
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@ package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
@@ -36,7 +37,7 @@ func (t FileType) String() string {
|
||||
case TypeTemp:
|
||||
return "temp"
|
||||
}
|
||||
return "<unknown>"
|
||||
return fmt.Sprintf("<unknown:%d>", t)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -66,7 +67,7 @@ type Writer interface {
|
||||
Syncer
|
||||
}
|
||||
|
||||
// File is the file.
|
||||
// File is the file. A file instance must be goroutine-safe.
|
||||
type File interface {
|
||||
// Open opens the file for read. Returns os.ErrNotExist error
|
||||
// if the file does not exist.
|
||||
@@ -93,7 +94,7 @@ type File interface {
|
||||
Remove() error
|
||||
}
|
||||
|
||||
// Storage is the storage.
|
||||
// Storage is the storage. A storage instance must be goroutine-safe.
|
||||
type Storage interface {
|
||||
// Lock locks the storage. Any subsequent attempt to call Lock will fail
|
||||
// until the last lock released.
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
const typeShift = 3
|
||||
const typeShift = 4
|
||||
|
||||
var (
|
||||
tsErrInvalidFile = errors.New("leveldb.testStorage: invalid file for argument")
|
||||
|
||||
+203
-114
@@ -7,11 +7,12 @@
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/cache"
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
@@ -19,34 +20,41 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// table file
|
||||
// tFile holds basic information about a table.
|
||||
type tFile struct {
|
||||
file storage.File
|
||||
seekLeft int32
|
||||
size uint64
|
||||
min, max iKey
|
||||
file storage.File
|
||||
seekLeft int32
|
||||
size uint64
|
||||
imin, imax iKey
|
||||
}
|
||||
|
||||
// test if key is after t
|
||||
func (t *tFile) isAfter(key []byte, ucmp comparer.BasicComparer) bool {
|
||||
return key != nil && ucmp.Compare(key, t.max.ukey()) > 0
|
||||
// Returns true if given key is after largest key of this table.
|
||||
func (t *tFile) after(icmp *iComparer, ukey []byte) bool {
|
||||
return ukey != nil && icmp.uCompare(ukey, t.imax.ukey()) > 0
|
||||
}
|
||||
|
||||
// test if key is before t
|
||||
func (t *tFile) isBefore(key []byte, ucmp comparer.BasicComparer) bool {
|
||||
return key != nil && ucmp.Compare(key, t.min.ukey()) < 0
|
||||
// Returns true if given key is before smallest key of this table.
|
||||
func (t *tFile) before(icmp *iComparer, ukey []byte) bool {
|
||||
return ukey != nil && icmp.uCompare(ukey, t.imin.ukey()) < 0
|
||||
}
|
||||
|
||||
func (t *tFile) incrSeek() int32 {
|
||||
// Returns true if given key range overlaps with this table key range.
|
||||
func (t *tFile) overlaps(icmp *iComparer, umin, umax []byte) bool {
|
||||
return !t.after(icmp, umin) && !t.before(icmp, umax)
|
||||
}
|
||||
|
||||
// Cosumes one seek and return current seeks left.
|
||||
func (t *tFile) consumeSeek() int32 {
|
||||
return atomic.AddInt32(&t.seekLeft, -1)
|
||||
}
|
||||
|
||||
func newTFile(file storage.File, size uint64, min, max iKey) *tFile {
|
||||
// Creates new tFile.
|
||||
func newTableFile(file storage.File, size uint64, imin, imax iKey) *tFile {
|
||||
f := &tFile{
|
||||
file: file,
|
||||
size: size,
|
||||
min: min,
|
||||
max: max,
|
||||
imin: imin,
|
||||
imax: imax,
|
||||
}
|
||||
|
||||
// We arrange to automatically compact this file after
|
||||
@@ -70,33 +78,40 @@ func newTFile(file storage.File, size uint64, min, max iKey) *tFile {
|
||||
return f
|
||||
}
|
||||
|
||||
// table files
|
||||
// tFiles hold multiple tFile.
|
||||
type tFiles []*tFile
|
||||
|
||||
func (tf tFiles) Len() int { return len(tf) }
|
||||
func (tf tFiles) Swap(i, j int) { tf[i], tf[j] = tf[j], tf[i] }
|
||||
|
||||
// Returns true if i smallest key is less than j.
|
||||
// This used for sort by key in ascending order.
|
||||
func (tf tFiles) lessByKey(icmp *iComparer, i, j int) bool {
|
||||
a, b := tf[i], tf[j]
|
||||
n := icmp.Compare(a.min, b.min)
|
||||
n := icmp.Compare(a.imin, b.imin)
|
||||
if n == 0 {
|
||||
return a.file.Num() < b.file.Num()
|
||||
}
|
||||
return n < 0
|
||||
}
|
||||
|
||||
// Returns true if i file number is greater than j.
|
||||
// This used for sort by file number in descending order.
|
||||
func (tf tFiles) lessByNum(i, j int) bool {
|
||||
return tf[i].file.Num() > tf[j].file.Num()
|
||||
}
|
||||
|
||||
// Sorts tables by key in ascending order.
|
||||
func (tf tFiles) sortByKey(icmp *iComparer) {
|
||||
sort.Sort(&tFilesSortByKey{tFiles: tf, icmp: icmp})
|
||||
}
|
||||
|
||||
// Sorts tables by file number in descending order.
|
||||
func (tf tFiles) sortByNum() {
|
||||
sort.Sort(&tFilesSortByNum{tFiles: tf})
|
||||
}
|
||||
|
||||
// Returns sum of all tables size.
|
||||
func (tf tFiles) size() (sum uint64) {
|
||||
for _, t := range tf {
|
||||
sum += t.size
|
||||
@@ -104,96 +119,106 @@ func (tf tFiles) size() (sum uint64) {
|
||||
return sum
|
||||
}
|
||||
|
||||
func (tf tFiles) searchMin(key iKey, icmp *iComparer) int {
|
||||
// Searches smallest index of tables whose its smallest
|
||||
// key is after or equal with given key.
|
||||
func (tf tFiles) searchMin(icmp *iComparer, ikey iKey) int {
|
||||
return sort.Search(len(tf), func(i int) bool {
|
||||
return icmp.Compare(tf[i].min, key) >= 0
|
||||
return icmp.Compare(tf[i].imin, ikey) >= 0
|
||||
})
|
||||
}
|
||||
|
||||
func (tf tFiles) searchMax(key iKey, icmp *iComparer) int {
|
||||
// Searches smallest index of tables whose its largest
|
||||
// key is after or equal with given key.
|
||||
func (tf tFiles) searchMax(icmp *iComparer, ikey iKey) int {
|
||||
return sort.Search(len(tf), func(i int) bool {
|
||||
return icmp.Compare(tf[i].max, key) >= 0
|
||||
return icmp.Compare(tf[i].imax, ikey) >= 0
|
||||
})
|
||||
}
|
||||
|
||||
func (tf tFiles) isOverlaps(min, max []byte, disjSorted bool, icmp *iComparer) bool {
|
||||
ucmp := icmp.cmp
|
||||
|
||||
if !disjSorted {
|
||||
// Need to check against all files
|
||||
// Returns true if given key range overlaps with one or more
|
||||
// tables key range. If unsorted is true then binary search will not be used.
|
||||
func (tf tFiles) overlaps(icmp *iComparer, umin, umax []byte, unsorted bool) bool {
|
||||
if unsorted {
|
||||
// Check against all files.
|
||||
for _, t := range tf {
|
||||
if !t.isAfter(min, ucmp) && !t.isBefore(max, ucmp) {
|
||||
if t.overlaps(icmp, umin, umax) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var idx int
|
||||
if len(min) > 0 {
|
||||
// Find the earliest possible internal key for min
|
||||
idx = tf.searchMax(newIKey(min, kMaxSeq, tSeek), icmp)
|
||||
i := 0
|
||||
if len(umin) > 0 {
|
||||
// Find the earliest possible internal key for min.
|
||||
i = tf.searchMax(icmp, newIKey(umin, kMaxSeq, tSeek))
|
||||
}
|
||||
|
||||
if idx >= len(tf) {
|
||||
// beginning of range is after all files, so no overlap
|
||||
if i >= len(tf) {
|
||||
// Beginning of range is after all files, so no overlap.
|
||||
return false
|
||||
}
|
||||
return !tf[idx].isBefore(max, ucmp)
|
||||
return !tf[i].before(icmp, umax)
|
||||
}
|
||||
|
||||
func (tf tFiles) getOverlaps(min, max []byte, r *tFiles, disjSorted bool, ucmp comparer.BasicComparer) {
|
||||
// Returns tables whose its key range overlaps with given key range.
|
||||
// If overlapped is true then the search will be expanded to tables that
|
||||
// overlaps with each other.
|
||||
func (tf tFiles) getOverlaps(dst tFiles, icmp *iComparer, umin, umax []byte, overlapped bool) tFiles {
|
||||
x := len(dst)
|
||||
for i := 0; i < len(tf); {
|
||||
t := tf[i]
|
||||
i++
|
||||
if t.isAfter(min, ucmp) || t.isBefore(max, ucmp) {
|
||||
continue
|
||||
}
|
||||
|
||||
*r = append(*r, t)
|
||||
if !disjSorted {
|
||||
// Level-0 files may overlap each other. So check if the newly
|
||||
// added file has expanded the range. If so, restart search.
|
||||
if min != nil && ucmp.Compare(t.min.ukey(), min) < 0 {
|
||||
min = t.min.ukey()
|
||||
*r = nil
|
||||
i = 0
|
||||
} else if max != nil && ucmp.Compare(t.max.ukey(), max) > 0 {
|
||||
max = t.max.ukey()
|
||||
*r = nil
|
||||
i = 0
|
||||
if t.overlaps(icmp, umin, umax) {
|
||||
if overlapped {
|
||||
// For overlapped files, check if the newly added file has
|
||||
// expanded the range. If so, restart search.
|
||||
if umin != nil && icmp.uCompare(t.imin.ukey(), umin) < 0 {
|
||||
umin = t.imin.ukey()
|
||||
dst = dst[:x]
|
||||
i = 0
|
||||
continue
|
||||
} else if umax != nil && icmp.uCompare(t.imax.ukey(), umax) > 0 {
|
||||
umax = t.imax.ukey()
|
||||
dst = dst[:x]
|
||||
i = 0
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dst = append(dst, t)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
return dst
|
||||
}
|
||||
|
||||
func (tf tFiles) getRange(icmp *iComparer) (min, max iKey) {
|
||||
// Returns tables key range.
|
||||
func (tf tFiles) getRange(icmp *iComparer) (imin, imax iKey) {
|
||||
for i, t := range tf {
|
||||
if i == 0 {
|
||||
min, max = t.min, t.max
|
||||
imin, imax = t.imin, t.imax
|
||||
continue
|
||||
}
|
||||
if icmp.Compare(t.min, min) < 0 {
|
||||
min = t.min
|
||||
if icmp.Compare(t.imin, imin) < 0 {
|
||||
imin = t.imin
|
||||
}
|
||||
if icmp.Compare(t.max, max) > 0 {
|
||||
max = t.max
|
||||
if icmp.Compare(t.imax, imax) > 0 {
|
||||
imax = t.imax
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Creates iterator index from tables.
|
||||
func (tf tFiles) newIndexIterator(tops *tOps, icmp *iComparer, slice *util.Range, ro *opt.ReadOptions) iterator.IteratorIndexer {
|
||||
if slice != nil {
|
||||
var start, limit int
|
||||
if slice.Start != nil {
|
||||
start = tf.searchMax(iKey(slice.Start), icmp)
|
||||
start = tf.searchMax(icmp, iKey(slice.Start))
|
||||
}
|
||||
if slice.Limit != nil {
|
||||
limit = tf.searchMin(iKey(slice.Limit), icmp)
|
||||
limit = tf.searchMin(icmp, iKey(slice.Limit))
|
||||
} else {
|
||||
limit = tf.Len()
|
||||
}
|
||||
@@ -208,6 +233,7 @@ func (tf tFiles) newIndexIterator(tops *tOps, icmp *iComparer, slice *util.Range
|
||||
})
|
||||
}
|
||||
|
||||
// Tables iterator index.
|
||||
type tFilesArrayIndexer struct {
|
||||
tFiles
|
||||
tops *tOps
|
||||
@@ -217,7 +243,7 @@ type tFilesArrayIndexer struct {
|
||||
}
|
||||
|
||||
func (a *tFilesArrayIndexer) Search(key []byte) int {
|
||||
return a.searchMax(iKey(key), a.icmp)
|
||||
return a.searchMax(a.icmp, iKey(key))
|
||||
}
|
||||
|
||||
func (a *tFilesArrayIndexer) Get(i int) iterator.Iterator {
|
||||
@@ -227,6 +253,7 @@ func (a *tFilesArrayIndexer) Get(i int) iterator.Iterator {
|
||||
return a.tops.newIterator(a.tFiles[i], nil, a.ro)
|
||||
}
|
||||
|
||||
// Helper type for sortByKey.
|
||||
type tFilesSortByKey struct {
|
||||
tFiles
|
||||
icmp *iComparer
|
||||
@@ -236,6 +263,7 @@ func (x *tFilesSortByKey) Less(i, j int) bool {
|
||||
return x.lessByKey(x.icmp, i, j)
|
||||
}
|
||||
|
||||
// Helper type for sortByNum.
|
||||
type tFilesSortByNum struct {
|
||||
tFiles
|
||||
}
|
||||
@@ -244,19 +272,17 @@ func (x *tFilesSortByNum) Less(i, j int) bool {
|
||||
return x.lessByNum(i, j)
|
||||
}
|
||||
|
||||
// table operations
|
||||
// Table operations.
|
||||
type tOps struct {
|
||||
s *session
|
||||
cache cache.Cache
|
||||
cacheNS cache.Namespace
|
||||
bpool *util.BufferPool
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newTableOps(s *session, cacheCap int) *tOps {
|
||||
c := cache.NewLRUCache(cacheCap)
|
||||
ns := c.GetNamespace(0)
|
||||
return &tOps{s, c, ns}
|
||||
}
|
||||
|
||||
// Creates an empty table and returns table writer.
|
||||
func (t *tOps) create() (*tWriter, error) {
|
||||
file := t.s.getTableFile(t.s.allocFileNum())
|
||||
fw, err := file.Create()
|
||||
@@ -271,10 +297,11 @@ func (t *tOps) create() (*tWriter, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Builds table from src iterator.
|
||||
func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) {
|
||||
w, err := t.create()
|
||||
if err != nil {
|
||||
return f, n, err
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -284,7 +311,7 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) {
|
||||
}()
|
||||
|
||||
for src.Next() {
|
||||
err = w.add(src.Key(), src.Value())
|
||||
err = w.append(src.Key(), src.Value())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -299,84 +326,142 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (t *tOps) lookup(f *tFile) (c cache.Object, err error) {
|
||||
type trWrapper struct {
|
||||
*table.Reader
|
||||
t *tOps
|
||||
ref int
|
||||
}
|
||||
|
||||
func (w *trWrapper) Release() {
|
||||
if w.ref != 0 && !w.t.closed {
|
||||
panic(fmt.Sprintf("BUG: invalid ref %d, refer to issue #72", w.ref))
|
||||
}
|
||||
w.Reader.Release()
|
||||
}
|
||||
|
||||
type trCacheHandleWrapper struct {
|
||||
cache.Handle
|
||||
t *tOps
|
||||
released bool
|
||||
}
|
||||
|
||||
func (w *trCacheHandleWrapper) Release() {
|
||||
w.t.mu.Lock()
|
||||
defer w.t.mu.Unlock()
|
||||
|
||||
if !w.released {
|
||||
w.released = true
|
||||
w.Value().(*trWrapper).ref--
|
||||
}
|
||||
w.Handle.Release()
|
||||
}
|
||||
|
||||
// Opens table. It returns a cache handle, which should
|
||||
// be released after use.
|
||||
func (t *tOps) open(f *tFile) (ch cache.Handle, err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
num := f.file.Num()
|
||||
c, ok := t.cacheNS.Get(num, func() (ok bool, value interface{}, charge int, fin cache.SetFin) {
|
||||
ch = t.cacheNS.Get(num, func() (charge int, value interface{}) {
|
||||
var r storage.Reader
|
||||
r, err = f.file.Open()
|
||||
if err != nil {
|
||||
return
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
o := t.s.o
|
||||
|
||||
var cacheNS cache.Namespace
|
||||
if bc := o.GetBlockCache(); bc != nil {
|
||||
cacheNS = bc.GetNamespace(num)
|
||||
var bcacheNS cache.Namespace
|
||||
if bc := t.s.o.GetBlockCache(); bc != nil {
|
||||
bcacheNS = bc.GetNamespace(num)
|
||||
}
|
||||
|
||||
ok = true
|
||||
value = table.NewReader(r, int64(f.size), cacheNS, o)
|
||||
charge = 1
|
||||
fin = func() {
|
||||
r.Close()
|
||||
}
|
||||
return
|
||||
return 1, &trWrapper{table.NewReader(r, int64(f.size), bcacheNS, t.bpool, t.s.o), t, 0}
|
||||
})
|
||||
if !ok && err == nil {
|
||||
if ch == nil && err == nil {
|
||||
err = ErrClosed
|
||||
}
|
||||
ch.Value().(*trWrapper).ref++
|
||||
ch = &trCacheHandleWrapper{ch, t, false}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *tOps) get(f *tFile, key []byte, ro *opt.ReadOptions) (rkey, rvalue []byte, err error) {
|
||||
c, err := t.lookup(f)
|
||||
// Finds key/value pair whose key is greater than or equal to the
|
||||
// given key.
|
||||
func (t *tOps) find(f *tFile, key []byte, ro *opt.ReadOptions) (rkey, rvalue []byte, err error) {
|
||||
ch, err := t.open(f)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer c.Release()
|
||||
return c.Value().(*table.Reader).Find(key, ro)
|
||||
defer ch.Release()
|
||||
return ch.Value().(*trWrapper).Find(key, ro)
|
||||
}
|
||||
|
||||
func (t *tOps) getApproximateOffset(f *tFile, key []byte) (offset uint64, err error) {
|
||||
c, err := t.lookup(f)
|
||||
// Returns approximate offset of the given key.
|
||||
func (t *tOps) offsetOf(f *tFile, key []byte) (offset uint64, err error) {
|
||||
ch, err := t.open(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_offset, err := c.Value().(*table.Reader).GetApproximateOffset(key)
|
||||
offset = uint64(_offset)
|
||||
c.Release()
|
||||
return
|
||||
defer ch.Release()
|
||||
offset_, err := ch.Value().(*trWrapper).OffsetOf(key)
|
||||
return uint64(offset_), err
|
||||
}
|
||||
|
||||
// Creates an iterator from the given table.
|
||||
func (t *tOps) newIterator(f *tFile, slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
c, err := t.lookup(f)
|
||||
ch, err := t.open(f)
|
||||
if err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
iter := c.Value().(*table.Reader).NewIterator(slice, ro)
|
||||
iter.SetReleaser(c)
|
||||
iter := ch.Value().(*trWrapper).NewIterator(slice, ro)
|
||||
iter.SetReleaser(ch)
|
||||
return iter
|
||||
}
|
||||
|
||||
// Removes table from persistent storage. It waits until
|
||||
// no one use the the table.
|
||||
func (t *tOps) remove(f *tFile) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
num := f.file.Num()
|
||||
t.cacheNS.Delete(num, func(exist bool) {
|
||||
if err := f.file.Remove(); err != nil {
|
||||
t.s.logf("table@remove removing @%d %q", num, err)
|
||||
} else {
|
||||
t.s.logf("table@remove removed @%d", num)
|
||||
}
|
||||
if bc := t.s.o.GetBlockCache(); bc != nil {
|
||||
bc.GetNamespace(num).Zap(false)
|
||||
t.cacheNS.Delete(num, func(exist, pending bool) {
|
||||
if !pending {
|
||||
if err := f.file.Remove(); err != nil {
|
||||
t.s.logf("table@remove removing @%d %q", num, err)
|
||||
} else {
|
||||
t.s.logf("table@remove removed @%d", num)
|
||||
}
|
||||
if bc := t.s.o.GetBlockCache(); bc != nil {
|
||||
bc.ZapNamespace(num)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Closes the table ops instance. It will close all tables,
|
||||
// regadless still used or not.
|
||||
func (t *tOps) close() {
|
||||
t.cache.Zap(true)
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.closed = true
|
||||
t.cache.Zap()
|
||||
t.bpool.Close()
|
||||
}
|
||||
|
||||
// Creates new initialized table ops instance.
|
||||
func newTableOps(s *session, cacheCap int) *tOps {
|
||||
c := cache.NewLRUCache(cacheCap)
|
||||
return &tOps{
|
||||
s: s,
|
||||
cache: c,
|
||||
cacheNS: c.GetNamespace(0),
|
||||
bpool: util.NewBufferPool(s.o.GetBlockSize() + 5),
|
||||
}
|
||||
}
|
||||
|
||||
// tWriter wraps the table writer. It keep track of file descriptor
|
||||
// and added key range.
|
||||
type tWriter struct {
|
||||
t *tOps
|
||||
|
||||
@@ -387,7 +472,8 @@ type tWriter struct {
|
||||
first, last []byte
|
||||
}
|
||||
|
||||
func (w *tWriter) add(key, value []byte) error {
|
||||
// Append key/value pair to the table.
|
||||
func (w *tWriter) append(key, value []byte) error {
|
||||
if w.first == nil {
|
||||
w.first = append([]byte{}, key...)
|
||||
}
|
||||
@@ -395,10 +481,12 @@ func (w *tWriter) add(key, value []byte) error {
|
||||
return w.tw.Append(key, value)
|
||||
}
|
||||
|
||||
// Returns true if the table is empty.
|
||||
func (w *tWriter) empty() bool {
|
||||
return w.first == nil
|
||||
}
|
||||
|
||||
// Finalizes the table and returns table file.
|
||||
func (w *tWriter) finish() (f *tFile, err error) {
|
||||
err = w.tw.Close()
|
||||
if err != nil {
|
||||
@@ -410,10 +498,11 @@ func (w *tWriter) finish() (f *tFile, err error) {
|
||||
return
|
||||
}
|
||||
w.w.Close()
|
||||
f = newTFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last))
|
||||
f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last))
|
||||
return
|
||||
}
|
||||
|
||||
// Drops the table.
|
||||
func (w *tWriter) drop() {
|
||||
w.w.Close()
|
||||
w.file.Remove()
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ var _ = testutil.Defer(func() {
|
||||
data := bw.buf.Bytes()
|
||||
restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:]))
|
||||
return &block{
|
||||
cmp: comparer.DefaultComparer,
|
||||
tr: &Reader{cmp: comparer.DefaultComparer},
|
||||
data: data,
|
||||
restartsLen: restartsLen,
|
||||
restartsOffset: len(data) - (restartsLen+1)*4,
|
||||
@@ -59,7 +59,7 @@ var _ = testutil.Defer(func() {
|
||||
// Make block.
|
||||
br := Build(kv, restartInterval)
|
||||
// Do testing.
|
||||
testutil.KeyValueTesting(nil, br, kv.Clone())
|
||||
testutil.KeyValueTesting(nil, kv.Clone(), br, nil, nil)
|
||||
}
|
||||
|
||||
Describe(Text(), Test)
|
||||
|
||||
+264
-105
@@ -13,6 +13,7 @@ import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.google.com/p/snappy-go/snappy"
|
||||
|
||||
@@ -25,8 +26,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrIterReleased = errors.New("leveldb/table: iterator released")
|
||||
ErrNotFound = util.ErrNotFound
|
||||
ErrReaderReleased = errors.New("leveldb/table: reader released")
|
||||
ErrIterReleased = errors.New("leveldb/table: iterator released")
|
||||
)
|
||||
|
||||
func max(x, y int) int {
|
||||
@@ -37,7 +39,7 @@ func max(x, y int) int {
|
||||
}
|
||||
|
||||
type block struct {
|
||||
cmp comparer.BasicComparer
|
||||
tr *Reader
|
||||
data []byte
|
||||
restartsLen int
|
||||
restartsOffset int
|
||||
@@ -46,31 +48,25 @@ type block struct {
|
||||
}
|
||||
|
||||
func (b *block) seek(rstart, rlimit int, key []byte) (index, offset int, err error) {
|
||||
n := b.restartsOffset
|
||||
data := b.data
|
||||
cmp := b.cmp
|
||||
|
||||
index = sort.Search(b.restartsLen-rstart-(b.restartsLen-rlimit), func(i int) bool {
|
||||
offset := int(binary.LittleEndian.Uint32(data[n+4*(rstart+i):]))
|
||||
offset += 1 // shared always zero, since this is a restart point
|
||||
v1, n1 := binary.Uvarint(data[offset:]) // key length
|
||||
_, n2 := binary.Uvarint(data[offset+n1:]) // value length
|
||||
offset := int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*(rstart+i):]))
|
||||
offset += 1 // shared always zero, since this is a restart point
|
||||
v1, n1 := binary.Uvarint(b.data[offset:]) // key length
|
||||
_, n2 := binary.Uvarint(b.data[offset+n1:]) // value length
|
||||
m := offset + n1 + n2
|
||||
return cmp.Compare(data[m:m+int(v1)], key) > 0
|
||||
return b.tr.cmp.Compare(b.data[m:m+int(v1)], key) > 0
|
||||
}) + rstart - 1
|
||||
if index < rstart {
|
||||
// The smallest key is greater-than key sought.
|
||||
index = rstart
|
||||
}
|
||||
offset = int(binary.LittleEndian.Uint32(data[n+4*index:]))
|
||||
offset = int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*index:]))
|
||||
return
|
||||
}
|
||||
|
||||
func (b *block) restartIndex(rstart, rlimit, offset int) int {
|
||||
n := b.restartsOffset
|
||||
data := b.data
|
||||
return sort.Search(b.restartsLen-rstart-(b.restartsLen-rlimit), func(i int) bool {
|
||||
return int(binary.LittleEndian.Uint32(data[n+4*(rstart+i):])) > offset
|
||||
return int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*(rstart+i):])) > offset
|
||||
}) + rstart - 1
|
||||
}
|
||||
|
||||
@@ -139,6 +135,12 @@ func (b *block) newIterator(slice *util.Range, inclLimit bool, cache util.Releas
|
||||
return bi
|
||||
}
|
||||
|
||||
func (b *block) Release() {
|
||||
b.tr.bpool.Put(b.data)
|
||||
b.tr = nil
|
||||
b.data = nil
|
||||
}
|
||||
|
||||
type dir int
|
||||
|
||||
const (
|
||||
@@ -261,7 +263,7 @@ func (i *blockIter) Seek(key []byte) bool {
|
||||
i.dir = dirForward
|
||||
}
|
||||
for i.Next() {
|
||||
if i.block.cmp.Compare(i.key, key) >= 0 {
|
||||
if i.block.tr.cmp.Compare(i.key, key) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -437,25 +439,32 @@ func (i *blockIter) Value() []byte {
|
||||
}
|
||||
|
||||
func (i *blockIter) Release() {
|
||||
i.prevNode = nil
|
||||
i.prevKeys = nil
|
||||
i.key = nil
|
||||
i.value = nil
|
||||
i.dir = dirReleased
|
||||
if i.cache != nil {
|
||||
i.cache.Release()
|
||||
i.cache = nil
|
||||
}
|
||||
if i.releaser != nil {
|
||||
i.releaser.Release()
|
||||
i.releaser = nil
|
||||
if i.dir != dirReleased {
|
||||
i.block = nil
|
||||
i.prevNode = nil
|
||||
i.prevKeys = nil
|
||||
i.key = nil
|
||||
i.value = nil
|
||||
i.dir = dirReleased
|
||||
if i.cache != nil {
|
||||
i.cache.Release()
|
||||
i.cache = nil
|
||||
}
|
||||
if i.releaser != nil {
|
||||
i.releaser.Release()
|
||||
i.releaser = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *blockIter) SetReleaser(releaser util.Releaser) {
|
||||
if i.dir > dirReleased {
|
||||
i.releaser = releaser
|
||||
if i.dir == dirReleased {
|
||||
panic(util.ErrReleased)
|
||||
}
|
||||
if i.releaser != nil && releaser != nil {
|
||||
panic(util.ErrHasReleaser)
|
||||
}
|
||||
i.releaser = releaser
|
||||
}
|
||||
|
||||
func (i *blockIter) Valid() bool {
|
||||
@@ -467,7 +476,7 @@ func (i *blockIter) Error() error {
|
||||
}
|
||||
|
||||
type filterBlock struct {
|
||||
filter filter.Filter
|
||||
tr *Reader
|
||||
data []byte
|
||||
oOffset int
|
||||
baseLg uint
|
||||
@@ -481,7 +490,7 @@ func (b *filterBlock) contains(offset uint64, key []byte) bool {
|
||||
n := int(binary.LittleEndian.Uint32(o))
|
||||
m := int(binary.LittleEndian.Uint32(o[4:]))
|
||||
if n < m && m <= b.oOffset {
|
||||
return b.filter.Contains(b.data[n:m], key)
|
||||
return b.tr.filter.Contains(b.data[n:m], key)
|
||||
} else if n == m {
|
||||
return false
|
||||
}
|
||||
@@ -489,10 +498,15 @@ func (b *filterBlock) contains(offset uint64, key []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *filterBlock) Release() {
|
||||
b.tr.bpool.Put(b.data)
|
||||
b.tr = nil
|
||||
b.data = nil
|
||||
}
|
||||
|
||||
type indexIter struct {
|
||||
blockIter
|
||||
tableReader *Reader
|
||||
slice *util.Range
|
||||
*blockIter
|
||||
slice *util.Range
|
||||
// Options
|
||||
checksum bool
|
||||
fillCache bool
|
||||
@@ -507,27 +521,31 @@ func (i *indexIter) Get() iterator.Iterator {
|
||||
if n == 0 {
|
||||
return iterator.NewEmptyIterator(errors.New("leveldb/table: Reader: invalid table (bad data block handle)"))
|
||||
}
|
||||
|
||||
var slice *util.Range
|
||||
if i.slice != nil && (i.blockIter.isFirst() || i.blockIter.isLast()) {
|
||||
slice = i.slice
|
||||
}
|
||||
return i.tableReader.getDataIter(dataBH, slice, i.checksum, i.fillCache)
|
||||
return i.blockIter.block.tr.getDataIterErr(dataBH, slice, i.checksum, i.fillCache)
|
||||
}
|
||||
|
||||
// Reader is a table reader.
|
||||
type Reader struct {
|
||||
mu sync.RWMutex
|
||||
reader io.ReaderAt
|
||||
cache cache.Namespace
|
||||
err error
|
||||
bpool *util.BufferPool
|
||||
// Options
|
||||
cmp comparer.Comparer
|
||||
filter filter.Filter
|
||||
checksum bool
|
||||
strictIter bool
|
||||
|
||||
dataEnd int64
|
||||
indexBlock *block
|
||||
filterBlock *filterBlock
|
||||
dataEnd int64
|
||||
indexBH, filterBH blockHandle
|
||||
indexBlock *block
|
||||
filterBlock *filterBlock
|
||||
}
|
||||
|
||||
func verifyChecksum(data []byte) bool {
|
||||
@@ -538,12 +556,13 @@ func verifyChecksum(data []byte) bool {
|
||||
}
|
||||
|
||||
func (r *Reader) readRawBlock(bh blockHandle, checksum bool) ([]byte, error) {
|
||||
data := make([]byte, bh.length+blockTrailerLen)
|
||||
data := r.bpool.Get(int(bh.length + blockTrailerLen))
|
||||
if _, err := r.reader.ReadAt(data, int64(bh.offset)); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if checksum || r.checksum {
|
||||
if !verifyChecksum(data) {
|
||||
r.bpool.Put(data)
|
||||
return nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)")
|
||||
}
|
||||
}
|
||||
@@ -551,12 +570,18 @@ func (r *Reader) readRawBlock(bh blockHandle, checksum bool) ([]byte, error) {
|
||||
case blockTypeNoCompression:
|
||||
data = data[:bh.length]
|
||||
case blockTypeSnappyCompression:
|
||||
var err error
|
||||
data, err = snappy.Decode(nil, data[:bh.length])
|
||||
decLen, err := snappy.DecodedLen(data[:bh.length])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmp := data
|
||||
data, err = snappy.Decode(r.bpool.Get(decLen), tmp[:bh.length])
|
||||
r.bpool.Put(tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
r.bpool.Put(data)
|
||||
return nil, fmt.Errorf("leveldb/table: Reader: unknown block compression type: %d", data[bh.length])
|
||||
}
|
||||
return data, nil
|
||||
@@ -569,7 +594,7 @@ func (r *Reader) readBlock(bh blockHandle, checksum bool) (*block, error) {
|
||||
}
|
||||
restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:]))
|
||||
b := &block{
|
||||
cmp: r.cmp,
|
||||
tr: r,
|
||||
data: data,
|
||||
restartsLen: restartsLen,
|
||||
restartsOffset: len(data) - (restartsLen+1)*4,
|
||||
@@ -578,7 +603,44 @@ func (r *Reader) readBlock(bh blockHandle, checksum bool) (*block, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterBlock, error) {
|
||||
func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*block, util.Releaser, error) {
|
||||
if r.cache != nil {
|
||||
var err error
|
||||
ch := r.cache.Get(bh.offset, func() (charge int, value interface{}) {
|
||||
if !fillCache {
|
||||
return 0, nil
|
||||
}
|
||||
var b *block
|
||||
b, err = r.readBlock(bh, checksum)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
return cap(b.data), b
|
||||
})
|
||||
if ch != nil {
|
||||
b, ok := ch.Value().(*block)
|
||||
if !ok {
|
||||
ch.Release()
|
||||
return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type")
|
||||
}
|
||||
if !b.checksum && (r.checksum || checksum) {
|
||||
if !verifyChecksum(b.data) {
|
||||
ch.Release()
|
||||
return nil, nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)")
|
||||
}
|
||||
b.checksum = true
|
||||
}
|
||||
return b, ch, err
|
||||
} else if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
b, err := r.readBlock(bh, checksum)
|
||||
return b, b, err
|
||||
}
|
||||
|
||||
func (r *Reader) readFilterBlock(bh blockHandle) (*filterBlock, error) {
|
||||
data, err := r.readRawBlock(bh, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -593,7 +655,7 @@ func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterB
|
||||
return nil, errors.New("leveldb/table: Reader: invalid filter block (invalid offset)")
|
||||
}
|
||||
b := &filterBlock{
|
||||
filter: filter,
|
||||
tr: r,
|
||||
data: data,
|
||||
oOffset: oOffset,
|
||||
baseLg: uint(data[n-1]),
|
||||
@@ -602,44 +664,67 @@ func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterB
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator {
|
||||
func (r *Reader) readFilterBlockCached(bh blockHandle, fillCache bool) (*filterBlock, util.Releaser, error) {
|
||||
if r.cache != nil {
|
||||
// Get/set block cache.
|
||||
var err error
|
||||
cache, ok := r.cache.Get(dataBH.offset, func() (ok bool, value interface{}, charge int, fin cache.SetFin) {
|
||||
ch := r.cache.Get(bh.offset, func() (charge int, value interface{}) {
|
||||
if !fillCache {
|
||||
return
|
||||
return 0, nil
|
||||
}
|
||||
var dataBlock *block
|
||||
dataBlock, err = r.readBlock(dataBH, checksum)
|
||||
if err == nil {
|
||||
ok = true
|
||||
value = dataBlock
|
||||
charge = int(dataBH.length)
|
||||
var b *filterBlock
|
||||
b, err = r.readFilterBlock(bh)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
return
|
||||
return cap(b.data), b
|
||||
})
|
||||
if err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
if ok {
|
||||
dataBlock := cache.Value().(*block)
|
||||
if !dataBlock.checksum && (r.checksum || checksum) {
|
||||
if !verifyChecksum(dataBlock.data) {
|
||||
return iterator.NewEmptyIterator(errors.New("leveldb/table: Reader: invalid block (checksum mismatch)"))
|
||||
}
|
||||
dataBlock.checksum = true
|
||||
if ch != nil {
|
||||
b, ok := ch.Value().(*filterBlock)
|
||||
if !ok {
|
||||
ch.Release()
|
||||
return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type")
|
||||
}
|
||||
iter := dataBlock.newIterator(slice, false, cache)
|
||||
return iter
|
||||
return b, ch, err
|
||||
} else if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
dataBlock, err := r.readBlock(dataBH, checksum)
|
||||
|
||||
b, err := r.readFilterBlock(bh)
|
||||
return b, b, err
|
||||
}
|
||||
|
||||
func (r *Reader) getIndexBlock(fillCache bool) (b *block, rel util.Releaser, err error) {
|
||||
if r.indexBlock == nil {
|
||||
return r.readBlockCached(r.indexBH, true, fillCache)
|
||||
}
|
||||
return r.indexBlock, util.NoopReleaser{}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) getFilterBlock(fillCache bool) (*filterBlock, util.Releaser, error) {
|
||||
if r.filterBlock == nil {
|
||||
return r.readFilterBlockCached(r.filterBH, fillCache)
|
||||
}
|
||||
return r.filterBlock, util.NoopReleaser{}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator {
|
||||
b, rel, err := r.readBlockCached(dataBH, checksum, fillCache)
|
||||
if err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
iter := dataBlock.newIterator(slice, false, nil)
|
||||
return iter
|
||||
return b.newIterator(slice, false, rel)
|
||||
}
|
||||
|
||||
func (r *Reader) getDataIterErr(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if r.err != nil {
|
||||
return iterator.NewEmptyIterator(r.err)
|
||||
}
|
||||
|
||||
return r.getDataIter(dataBH, slice, checksum, fillCache)
|
||||
}
|
||||
|
||||
// NewIterator creates an iterator from the table.
|
||||
@@ -653,20 +738,26 @@ func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fi
|
||||
// when not used.
|
||||
//
|
||||
// Also read Iterator documentation of the leveldb/iterator package.
|
||||
|
||||
func (r *Reader) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if r.err != nil {
|
||||
return iterator.NewEmptyIterator(r.err)
|
||||
}
|
||||
|
||||
index := &indexIter{
|
||||
blockIter: *r.indexBlock.newIterator(slice, true, nil),
|
||||
tableReader: r,
|
||||
slice: slice,
|
||||
checksum: ro.GetStrict(opt.StrictBlockChecksum),
|
||||
fillCache: !ro.GetDontFillCache(),
|
||||
fillCache := !ro.GetDontFillCache()
|
||||
indexBlock, rel, err := r.getIndexBlock(fillCache)
|
||||
if err != nil {
|
||||
return iterator.NewEmptyIterator(err)
|
||||
}
|
||||
return iterator.NewIndexedIterator(index, r.strictIter || ro.GetStrict(opt.StrictIterator), false)
|
||||
index := &indexIter{
|
||||
blockIter: indexBlock.newIterator(slice, true, rel),
|
||||
slice: slice,
|
||||
checksum: ro.GetStrict(opt.StrictBlockChecksum),
|
||||
fillCache: !ro.GetDontFillCache(),
|
||||
}
|
||||
return iterator.NewIndexedIterator(index, r.strictIter || ro.GetStrict(opt.StrictIterator), true)
|
||||
}
|
||||
|
||||
// Find finds key/value pair whose key is greater than or equal to the
|
||||
@@ -676,12 +767,21 @@ func (r *Reader) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It
|
||||
// The caller should not modify the contents of the returned slice, but
|
||||
// it is safe to modify the contents of the argument after Find returns.
|
||||
func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if r.err != nil {
|
||||
err = r.err
|
||||
return
|
||||
}
|
||||
|
||||
index := r.indexBlock.newIterator(nil, true, nil)
|
||||
indexBlock, rel, err := r.getIndexBlock(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rel.Release()
|
||||
|
||||
index := indexBlock.newIterator(nil, true, nil)
|
||||
defer index.Release()
|
||||
if !index.Seek(key) {
|
||||
err = index.Error()
|
||||
@@ -695,9 +795,15 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err
|
||||
err = errors.New("leveldb/table: Reader: invalid table (bad data block handle)")
|
||||
return
|
||||
}
|
||||
if r.filterBlock != nil && !r.filterBlock.contains(dataBH.offset, key) {
|
||||
err = ErrNotFound
|
||||
return
|
||||
if r.filter != nil {
|
||||
filterBlock, rel, ferr := r.getFilterBlock(true)
|
||||
if ferr == nil {
|
||||
if !filterBlock.contains(dataBH.offset, key) {
|
||||
rel.Release()
|
||||
return nil, nil, ErrNotFound
|
||||
}
|
||||
rel.Release()
|
||||
}
|
||||
}
|
||||
data := r.getDataIter(dataBH, nil, ro.GetStrict(opt.StrictBlockChecksum), !ro.GetDontFillCache())
|
||||
defer data.Release()
|
||||
@@ -708,8 +814,15 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err
|
||||
}
|
||||
return
|
||||
}
|
||||
// Don't use block buffer, no need to copy the buffer.
|
||||
rkey = data.Key()
|
||||
value = data.Value()
|
||||
if r.bpool == nil {
|
||||
value = data.Value()
|
||||
} else {
|
||||
// Use block buffer, and since the buffer will be recycled, the buffer
|
||||
// need to be copied.
|
||||
value = append([]byte{}, data.Value()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -719,6 +832,9 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err
|
||||
// The caller should not modify the contents of the returned slice, but
|
||||
// it is safe to modify the contents of the argument after Get returns.
|
||||
func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if r.err != nil {
|
||||
err = r.err
|
||||
return
|
||||
@@ -732,16 +848,25 @@ func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error)
|
||||
return
|
||||
}
|
||||
|
||||
// GetApproximateOffset returns approximate offset for the given key.
|
||||
// OffsetOf returns approximate offset for the given key.
|
||||
//
|
||||
// It is safe to modify the contents of the argument after Get returns.
|
||||
func (r *Reader) GetApproximateOffset(key []byte) (offset int64, err error) {
|
||||
func (r *Reader) OffsetOf(key []byte) (offset int64, err error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if r.err != nil {
|
||||
err = r.err
|
||||
return
|
||||
}
|
||||
|
||||
index := r.indexBlock.newIterator(nil, true, nil)
|
||||
indexBlock, rel, err := r.readBlockCached(r.indexBH, true, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rel.Release()
|
||||
|
||||
index := indexBlock.newIterator(nil, true, nil)
|
||||
defer index.Release()
|
||||
if index.Seek(key) {
|
||||
dataBH, n := decodeBlockHandle(index.Value())
|
||||
@@ -759,12 +884,38 @@ func (r *Reader) GetApproximateOffset(key []byte) (offset int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Release implements util.Releaser.
|
||||
// It also close the file if it is an io.Closer.
|
||||
func (r *Reader) Release() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if closer, ok := r.reader.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
if r.indexBlock != nil {
|
||||
r.indexBlock.Release()
|
||||
r.indexBlock = nil
|
||||
}
|
||||
if r.filterBlock != nil {
|
||||
r.filterBlock.Release()
|
||||
r.filterBlock = nil
|
||||
}
|
||||
r.reader = nil
|
||||
r.cache = nil
|
||||
r.bpool = nil
|
||||
r.err = ErrReaderReleased
|
||||
}
|
||||
|
||||
// NewReader creates a new initialized table reader for the file.
|
||||
// The cache is optional and can be nil.
|
||||
func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options) *Reader {
|
||||
// The cache and bpool is optional and can be nil.
|
||||
//
|
||||
// The returned table reader instance is goroutine-safe.
|
||||
func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, bpool *util.BufferPool, o *opt.Options) *Reader {
|
||||
r := &Reader{
|
||||
reader: f,
|
||||
cache: cache,
|
||||
bpool: bpool,
|
||||
cmp: o.GetComparer(),
|
||||
checksum: o.GetStrict(opt.StrictBlockChecksum),
|
||||
strictIter: o.GetStrict(opt.StrictIterator),
|
||||
@@ -792,16 +943,11 @@ func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options)
|
||||
return r
|
||||
}
|
||||
// Decode the index block handle.
|
||||
indexBH, n := decodeBlockHandle(footer[n:])
|
||||
r.indexBH, n = decodeBlockHandle(footer[n:])
|
||||
if n == 0 {
|
||||
r.err = errors.New("leveldb/table: Reader: invalid table (bad index block handle)")
|
||||
return r
|
||||
}
|
||||
// Read index block.
|
||||
r.indexBlock, r.err = r.readBlock(indexBH, true)
|
||||
if r.err != nil {
|
||||
return r
|
||||
}
|
||||
// Read metaindex block.
|
||||
metaBlock, err := r.readBlock(metaBH, true)
|
||||
if err != nil {
|
||||
@@ -817,32 +963,45 @@ func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options)
|
||||
continue
|
||||
}
|
||||
fn := key[7:]
|
||||
var filter filter.Filter
|
||||
if f0 := o.GetFilter(); f0 != nil && f0.Name() == fn {
|
||||
filter = f0
|
||||
r.filter = f0
|
||||
} else {
|
||||
for _, f0 := range o.GetAltFilters() {
|
||||
if f0.Name() == fn {
|
||||
filter = f0
|
||||
r.filter = f0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter != nil {
|
||||
if r.filter != nil {
|
||||
filterBH, n := decodeBlockHandle(metaIter.Value())
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
r.filterBH = filterBH
|
||||
// Update data end.
|
||||
r.dataEnd = int64(filterBH.offset)
|
||||
filterBlock, err := r.readFilterBlock(filterBH, filter)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
r.filterBlock = filterBlock
|
||||
break
|
||||
}
|
||||
}
|
||||
metaIter.Release()
|
||||
metaBlock.Release()
|
||||
|
||||
// Cache index and filter block locally, since we don't have global cache.
|
||||
if cache == nil {
|
||||
r.indexBlock, r.err = r.readBlock(r.indexBH, true)
|
||||
if r.err != nil {
|
||||
return r
|
||||
}
|
||||
if r.filter != nil {
|
||||
r.filterBlock, err = r.readFilterBlock(r.filterBH)
|
||||
if err != nil {
|
||||
// Don't use filter then.
|
||||
r.filter = nil
|
||||
r.filterBH = blockHandle{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
+8
-6
@@ -59,9 +59,9 @@ var _ = testutil.Defer(func() {
|
||||
It("Should be able to approximate offset of a key correctly", func() {
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, o)
|
||||
tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o)
|
||||
CheckOffset := func(key string, expect, threshold int) {
|
||||
offset, err := tr.GetApproximateOffset([]byte(key))
|
||||
offset, err := tr.OffsetOf([]byte(key))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(offset).Should(BeNumerically("~", expect, threshold), "Offset of key %q", key)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ var _ = testutil.Defer(func() {
|
||||
tw.Close()
|
||||
|
||||
// Opening the table.
|
||||
tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, o)
|
||||
tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o)
|
||||
return tableWrapper{tr}
|
||||
}
|
||||
Test := func(kv *testutil.KeyValue, body func(r *Reader)) func() {
|
||||
@@ -104,14 +104,16 @@ var _ = testutil.Defer(func() {
|
||||
if body != nil {
|
||||
body(db.(tableWrapper).Reader)
|
||||
}
|
||||
testutil.KeyValueTesting(nil, db, *kv)
|
||||
testutil.KeyValueTesting(nil, *kv, db, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
testutil.AllKeyValueTesting(nil, Build)
|
||||
testutil.AllKeyValueTesting(nil, Build, nil, nil)
|
||||
Describe("with one key per block", Test(testutil.KeyValue_Generate(nil, 9, 1, 10, 512, 512), func(r *Reader) {
|
||||
It("should have correct blocks number", func() {
|
||||
Expect(r.indexBlock.restartsLen).Should(Equal(9))
|
||||
indexBlock, err := r.readBlock(r.indexBH, true)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(indexBlock.restartsLen).Should(Equal(9))
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
+57
-43
@@ -16,13 +16,22 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) {
|
||||
func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, teardown func(DB)) {
|
||||
if rnd == nil {
|
||||
rnd = NewRand()
|
||||
}
|
||||
|
||||
if db, ok := p.(Find); ok {
|
||||
It("Should find all keys with Find", func() {
|
||||
if p == nil {
|
||||
BeforeEach(func() {
|
||||
p = setup(kv)
|
||||
})
|
||||
AfterEach(func() {
|
||||
teardown(p)
|
||||
})
|
||||
}
|
||||
|
||||
It("Should find all keys with Find", func() {
|
||||
if db, ok := p.(Find); ok {
|
||||
ShuffledIndex(nil, kv.Len(), 1, func(i int) {
|
||||
key_, key, value := kv.IndexInexact(i)
|
||||
|
||||
@@ -38,9 +47,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) {
|
||||
Expect(rkey).Should(Equal(key))
|
||||
Expect(rvalue).Should(Equal(value), "Value for key %q (%q)", key_, key)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
It("Should return error if the key is not present", func() {
|
||||
It("Should return error if the key is not present", func() {
|
||||
if db, ok := p.(Find); ok {
|
||||
var key []byte
|
||||
if kv.Len() > 0 {
|
||||
key_, _ := kv.Index(kv.Len() - 1)
|
||||
@@ -49,11 +60,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) {
|
||||
rkey, _, err := db.TestFind(key)
|
||||
Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey)
|
||||
Expect(err).Should(Equal(util.ErrNotFound))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if db, ok := p.(Get); ok {
|
||||
It("Should only find exact key with Get", func() {
|
||||
It("Should only find exact key with Get", func() {
|
||||
if db, ok := p.(Get); ok {
|
||||
ShuffledIndex(nil, kv.Len(), 1, func(i int) {
|
||||
key_, key, value := kv.IndexInexact(i)
|
||||
|
||||
@@ -69,11 +80,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) {
|
||||
Expect(err).Should(Equal(util.ErrNotFound))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if db, ok := p.(NewIterator); ok {
|
||||
TestIter := func(r *util.Range, _kv KeyValue) {
|
||||
TestIter := func(r *util.Range, _kv KeyValue) {
|
||||
if db, ok := p.(NewIterator); ok {
|
||||
iter := db.TestNewIterator(r)
|
||||
Expect(iter.Error()).ShouldNot(HaveOccurred())
|
||||
|
||||
@@ -84,45 +95,48 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) {
|
||||
|
||||
DoIteratorTesting(&t)
|
||||
}
|
||||
}
|
||||
|
||||
It("Should iterates and seeks correctly", func(done Done) {
|
||||
TestIter(nil, kv.Clone())
|
||||
done <- true
|
||||
}, 3.0)
|
||||
It("Should iterates and seeks correctly", func(done Done) {
|
||||
TestIter(nil, kv.Clone())
|
||||
done <- true
|
||||
}, 3.0)
|
||||
|
||||
RandomIndex(rnd, kv.Len(), kv.Len(), func(i int) {
|
||||
type slice struct {
|
||||
r *util.Range
|
||||
start, limit int
|
||||
}
|
||||
RandomIndex(rnd, kv.Len(), kv.Len(), func(i int) {
|
||||
type slice struct {
|
||||
r *util.Range
|
||||
start, limit int
|
||||
}
|
||||
|
||||
key_, _, _ := kv.IndexInexact(i)
|
||||
for _, x := range []slice{
|
||||
{&util.Range{Start: key_, Limit: nil}, i, kv.Len()},
|
||||
{&util.Range{Start: nil, Limit: key_}, 0, i},
|
||||
} {
|
||||
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) {
|
||||
TestIter(x.r, kv.Slice(x.start, x.limit))
|
||||
done <- true
|
||||
}, 3.0)
|
||||
}
|
||||
})
|
||||
|
||||
RandomRange(rnd, kv.Len(), kv.Len(), func(start, limit int) {
|
||||
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) {
|
||||
r := kv.Range(start, limit)
|
||||
TestIter(&r, kv.Slice(start, limit))
|
||||
key_, _, _ := kv.IndexInexact(i)
|
||||
for _, x := range []slice{
|
||||
{&util.Range{Start: key_, Limit: nil}, i, kv.Len()},
|
||||
{&util.Range{Start: nil, Limit: key_}, 0, i},
|
||||
} {
|
||||
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) {
|
||||
TestIter(x.r, kv.Slice(x.start, x.limit))
|
||||
done <- true
|
||||
}, 3.0)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
RandomRange(rnd, kv.Len(), kv.Len(), func(start, limit int) {
|
||||
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) {
|
||||
r := kv.Range(start, limit)
|
||||
TestIter(&r, kv.Slice(start, limit))
|
||||
done <- true
|
||||
}, 3.0)
|
||||
})
|
||||
}
|
||||
|
||||
func AllKeyValueTesting(rnd *rand.Rand, body func(kv KeyValue) DB) {
|
||||
func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown func(DB)) {
|
||||
Test := func(kv *KeyValue) func() {
|
||||
return func() {
|
||||
db := body(*kv)
|
||||
KeyValueTesting(rnd, db, *kv)
|
||||
var p DB
|
||||
if body != nil {
|
||||
p = body(*kv)
|
||||
}
|
||||
KeyValueTesting(rnd, *kv, p, setup, teardown)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -57,6 +57,7 @@ const (
|
||||
typeManifest = iota
|
||||
typeJournal
|
||||
typeTable
|
||||
typeTemp
|
||||
|
||||
typeCount
|
||||
)
|
||||
@@ -91,6 +92,8 @@ func flattenType(m StorageMode, t storage.FileType) int {
|
||||
return x + typeJournal
|
||||
case storage.TypeTable:
|
||||
return x + typeTable
|
||||
case storage.TypeTemp:
|
||||
return x + typeTemp
|
||||
default:
|
||||
panic("invalid file type")
|
||||
}
|
||||
@@ -107,6 +110,8 @@ func listFlattenType(m StorageMode, t storage.FileType) []int {
|
||||
ret = append(ret, x+typeJournal)
|
||||
case t&storage.TypeTable != 0:
|
||||
ret = append(ret, x+typeTable)
|
||||
case t&storage.TypeTemp != 0:
|
||||
ret = append(ret, x+typeTemp)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ func (t *testingDB) TestClose() {
|
||||
func newTestingDB(o *opt.Options, ro *opt.ReadOptions, wo *opt.WriteOptions) *testingDB {
|
||||
stor := testutil.NewStorage()
|
||||
db, err := Open(stor, o)
|
||||
// FIXME: This may be called from outside It, which may cause panic.
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return &testingDB{
|
||||
DB: db,
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
b []byte
|
||||
miss int
|
||||
}
|
||||
|
||||
// BufferPool is a 'buffer pool'.
|
||||
type BufferPool struct {
|
||||
pool [6]chan []byte
|
||||
size [5]uint32
|
||||
sizeMiss [5]uint32
|
||||
sizeHalf [5]uint32
|
||||
baseline [4]int
|
||||
baselinex0 int
|
||||
baselinex1 int
|
||||
baseline0 int
|
||||
baseline1 int
|
||||
baseline2 int
|
||||
close chan struct{}
|
||||
|
||||
get uint32
|
||||
put uint32
|
||||
half uint32
|
||||
less uint32
|
||||
equal uint32
|
||||
greater uint32
|
||||
miss uint32
|
||||
}
|
||||
|
||||
func (p *BufferPool) poolNum(n int) int {
|
||||
if n <= p.baseline0 && n > p.baseline0/2 {
|
||||
return 0
|
||||
}
|
||||
for i, x := range p.baseline {
|
||||
if n <= x {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return len(p.baseline) + 1
|
||||
}
|
||||
|
||||
// Get returns buffer with length of n.
|
||||
func (p *BufferPool) Get(n int) []byte {
|
||||
if p == nil {
|
||||
return make([]byte, n)
|
||||
}
|
||||
|
||||
atomic.AddUint32(&p.get, 1)
|
||||
|
||||
poolNum := p.poolNum(n)
|
||||
pool := p.pool[poolNum]
|
||||
if poolNum == 0 {
|
||||
// Fast path.
|
||||
select {
|
||||
case b := <-pool:
|
||||
switch {
|
||||
case cap(b) > n:
|
||||
if cap(b)-n >= n {
|
||||
atomic.AddUint32(&p.half, 1)
|
||||
select {
|
||||
case pool <- b:
|
||||
default:
|
||||
}
|
||||
return make([]byte, n)
|
||||
} else {
|
||||
atomic.AddUint32(&p.less, 1)
|
||||
return b[:n]
|
||||
}
|
||||
case cap(b) == n:
|
||||
atomic.AddUint32(&p.equal, 1)
|
||||
return b[:n]
|
||||
default:
|
||||
atomic.AddUint32(&p.greater, 1)
|
||||
}
|
||||
default:
|
||||
atomic.AddUint32(&p.miss, 1)
|
||||
}
|
||||
|
||||
return make([]byte, n, p.baseline0)
|
||||
} else {
|
||||
sizePtr := &p.size[poolNum-1]
|
||||
|
||||
select {
|
||||
case b := <-pool:
|
||||
switch {
|
||||
case cap(b) > n:
|
||||
if cap(b)-n >= n {
|
||||
atomic.AddUint32(&p.half, 1)
|
||||
sizeHalfPtr := &p.sizeHalf[poolNum-1]
|
||||
if atomic.AddUint32(sizeHalfPtr, 1) == 20 {
|
||||
atomic.StoreUint32(sizePtr, uint32(cap(b)/2))
|
||||
atomic.StoreUint32(sizeHalfPtr, 0)
|
||||
} else {
|
||||
select {
|
||||
case pool <- b:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return make([]byte, n)
|
||||
} else {
|
||||
atomic.AddUint32(&p.less, 1)
|
||||
return b[:n]
|
||||
}
|
||||
case cap(b) == n:
|
||||
atomic.AddUint32(&p.equal, 1)
|
||||
return b[:n]
|
||||
default:
|
||||
atomic.AddUint32(&p.greater, 1)
|
||||
if uint32(cap(b)) >= atomic.LoadUint32(sizePtr) {
|
||||
select {
|
||||
case pool <- b:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
atomic.AddUint32(&p.miss, 1)
|
||||
}
|
||||
|
||||
if size := atomic.LoadUint32(sizePtr); uint32(n) > size {
|
||||
if size == 0 {
|
||||
atomic.CompareAndSwapUint32(sizePtr, 0, uint32(n))
|
||||
} else {
|
||||
sizeMissPtr := &p.sizeMiss[poolNum-1]
|
||||
if atomic.AddUint32(sizeMissPtr, 1) == 20 {
|
||||
atomic.StoreUint32(sizePtr, uint32(n))
|
||||
atomic.StoreUint32(sizeMissPtr, 0)
|
||||
}
|
||||
}
|
||||
return make([]byte, n)
|
||||
} else {
|
||||
return make([]byte, n, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put adds given buffer to the pool.
|
||||
func (p *BufferPool) Put(b []byte) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&p.put, 1)
|
||||
|
||||
pool := p.pool[p.poolNum(cap(b))]
|
||||
select {
|
||||
case pool <- b:
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *BufferPool) Close() {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case p.close <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BufferPool) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("BufferPool{B·%d Z·%v Zm·%v Zh·%v G·%d P·%d H·%d <·%d =·%d >·%d M·%d}",
|
||||
p.baseline0, p.size, p.sizeMiss, p.sizeHalf, p.get, p.put, p.half, p.less, p.equal, p.greater, p.miss)
|
||||
}
|
||||
|
||||
func (p *BufferPool) drain() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
for _, ch := range p.pool {
|
||||
select {
|
||||
case <-ch:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case <-p.close:
|
||||
for _, ch := range p.pool {
|
||||
close(ch)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewBufferPool creates a new initialized 'buffer pool'.
|
||||
func NewBufferPool(baseline int) *BufferPool {
|
||||
if baseline <= 0 {
|
||||
panic("baseline can't be <= 0")
|
||||
}
|
||||
p := &BufferPool{
|
||||
baseline0: baseline,
|
||||
baseline: [...]int{baseline / 4, baseline / 2, baseline * 2, baseline * 4},
|
||||
close: make(chan struct{}, 1),
|
||||
}
|
||||
for i, cap := range []int{2, 2, 4, 4, 2, 1} {
|
||||
p.pool[i] = make(chan []byte, cap)
|
||||
}
|
||||
go p.drain()
|
||||
return p
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user