Initial commit
This commit is contained in:
58
README
Normal file
58
README
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
go-iscsi
|
||||||
|
********
|
||||||
|
|
||||||
|
This project is a (c)go iscsi library, with the eventual aim of implementing a
|
||||||
|
"hydra" iSCSI implementation.
|
||||||
|
|
||||||
|
An hydra is an iSCSI target that, internally, takes requests and mirrors them
|
||||||
|
across multiple *real* iSCSI targets, with the aim of ensuring redundancy and
|
||||||
|
fault tolerance. The hydra (or Very Clever Proxy, as we could also call it) is
|
||||||
|
intended to be run on the same host as the iSCSI initiator (qemu, say) to
|
||||||
|
insulate the guest against the vagaries of disc failures, targets going away for
|
||||||
|
any reason, etc, etc.
|
||||||
|
|
||||||
|
This is being created specifically with redundant, fault-tolerant remote virtual
|
||||||
|
discs in mind, so performance and reliability are key. Looks like this:
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
* Minimal iSCSI target implementation
|
||||||
|
- This should contain as few features as we can get away with, and is what
|
||||||
|
qemu will connect to - ideally over localhost (iscsi doesn't seem to
|
||||||
|
support UNIX sockets or I'd use those)
|
||||||
|
* Complete iSCSI initiator implementation
|
||||||
|
- This needs to support as much of iSCSI as possible, and certainly
|
||||||
|
everything both QEMU and common real targets support
|
||||||
|
- Bindings to libiscsi are the way forward here
|
||||||
|
* Very Clever Proxy
|
||||||
|
- Takes a list of targets to keep in sync
|
||||||
|
- Runs the minimal target implementation
|
||||||
|
- Runs initiators for each of the real targets
|
||||||
|
- Proxies requests between local and remote targers, maintaining consistency
|
||||||
|
- Allows real targets to be swapped out for blanks that are then initialised
|
||||||
|
from the remaining targets
|
||||||
|
- One broken real target doesn't stall I/O for the real initiator
|
||||||
|
- Metadata is stored in reserved space on the real targets, and used to
|
||||||
|
ensure consistency if the Very Clever Proxy is killed at any stage and
|
||||||
|
brought back up on a different machine
|
||||||
|
- If a real target disconnects and then reconnects within a given window, it
|
||||||
|
can be brought back to consistency without reinitialising the whole disc
|
||||||
|
- Resizing is supported
|
||||||
|
+ On resize down, the resize is propagated to the real initiator as soon
|
||||||
|
as the first real target has announced its new size
|
||||||
|
+ On resize up, the resize is only propagated once all real targets have
|
||||||
|
announced a new size, and the propagated size is the smallest size they
|
||||||
|
all have in common
|
||||||
|
- The real initiator can submit requests asynchronously and have them dealt
|
||||||
|
with asynchronously
|
||||||
|
- Writes to the real targets happen concurrently, not serially, to avoid
|
||||||
|
multiplying write latency by the number of real targets
|
||||||
|
- Must be able to bind to a specific IP[4|6] address to enable IP-based ACLs
|
||||||
|
- Zero-copy, or as close to it as possible.
|
||||||
|
- Hides the first 1MiB of each real target for metadata to support the above
|
||||||
|
+ Hiding the end would be problematic on resizes
|
||||||
|
+ 1MiB is a complete crapshoot, 4KiB might be enough. Check.
|
||||||
|
|
||||||
|
DONE:
|
||||||
|
|
||||||
|
|
109
common/logger.go
Normal file
109
common/logger.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import(
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/syslog"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
useSyslog bool
|
||||||
|
verbosity int
|
||||||
|
|
||||||
|
syslog *syslog.Writer
|
||||||
|
stdlog *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewLogger(prefix string, useSyslog bool, syslogFailOutput io.Writer, verbosity int) (*Logger, error) {
|
||||||
|
out := Logger{
|
||||||
|
verbosity: verbosity,
|
||||||
|
useSyslog: useSyslog}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
out.stdlog = log.New(syslogFailOutput, prefix+" ", log.LstdFlags)
|
||||||
|
|
||||||
|
if useSyslog {
|
||||||
|
out.syslog, err = syslog.New(syslog.LOG_INFO, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return &out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (l *Logger) Debug(m string, v ...interface{}) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if l.verbosity < 2 { // -vv
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.useSyslog {
|
||||||
|
err = l.syslog.Debug(l.buildString("", m, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || !l.useSyslog {
|
||||||
|
l.stdlog.Println(l.buildString("DEBUG", m, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(m string, v ...interface{}) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if l.verbosity < 1 { // -v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.useSyslog {
|
||||||
|
err = l.syslog.Info(l.buildString("", m, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || !l.useSyslog {
|
||||||
|
l.stdlog.Println(l.buildString(" INFO", m, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(m string, v ...interface{}) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Warnings are always shown
|
||||||
|
|
||||||
|
if l.useSyslog {
|
||||||
|
err = l.syslog.Warning(l.buildString("", m, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || !l.useSyslog {
|
||||||
|
l.stdlog.Println(l.buildString(" WARN", m, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(m string, v ...interface{}) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Errors are always shown
|
||||||
|
|
||||||
|
if l.useSyslog {
|
||||||
|
err = l.syslog.Err(l.buildString("", m, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || !l.useSyslog {
|
||||||
|
l.stdlog.Println(l.buildString("ERROR", m, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) buildString(level, m string, v ...interface{}) string {
|
||||||
|
out := m
|
||||||
|
|
||||||
|
if level != "" {
|
||||||
|
out = level + ":" + m
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
out = fmt.Sprintf(out, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
38
hydra/cli-opts.go
Normal file
38
hydra/cli-opts.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package hydra
|
||||||
|
|
||||||
|
import(
|
||||||
|
"fmt"
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
"os"
|
||||||
|
"ur.gs/go-iscsi/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
TargetName string `short:"n" long:"name" required:"true" description:"Name of the iSCSI target we present."`
|
||||||
|
TargetListen string `short:"L" long:"listen" required:"true" description:"Address to listen on for incoming connections."`
|
||||||
|
|
||||||
|
InitiatorBind string `short:"b" long:"bind" description:"Address connections to the real targets will come from."`
|
||||||
|
InitiatorAddresses []string `short:"t" long:"target" required:"true" description:"A real target to keep in sync."`
|
||||||
|
|
||||||
|
Verbosity []bool `short:"v" long:"verbose" description:"Verbosity. Repeat multiple times to increase."`
|
||||||
|
Syslog bool `short:"s" long:"syslog" description:"Should we log to syslog? If not, logs go to stdout."`
|
||||||
|
|
||||||
|
Logger *common.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCommandLine(argv []string) (*Config, error) {
|
||||||
|
var config Config
|
||||||
|
_, err := flags.ParseArgs(&config, argv) ; if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logPrefix := fmt.Sprintf("iscsi-hydra:%s", config.TargetName)
|
||||||
|
logger, err := common.NewLogger(logPrefix, config.Syslog, os.Stdout, len(config.Verbosity)) ; if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Setting up logging failed: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Logger = logger
|
||||||
|
return &config, err
|
||||||
|
}
|
||||||
|
|
28
hydra/hydra.go
Normal file
28
hydra/hydra.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package hydra
|
||||||
|
|
||||||
|
import(
|
||||||
|
"ur.gs/go-iscsi/common"
|
||||||
|
_ "ur.gs/go-iscsi/initiator"
|
||||||
|
_ "ur.gs/go-iscsi/target"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hydra struct {
|
||||||
|
config *Config
|
||||||
|
log *common.Logger // Shortcut
|
||||||
|
targetListener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(targetListener net.Listener, config *Config) (*Hydra) {
|
||||||
|
return &Hydra{
|
||||||
|
config: config,
|
||||||
|
log: config.Logger,
|
||||||
|
targetListener: targetListener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
func (h *Hydra) Run() {
|
||||||
|
h.log.Debug("Entered hydra main loop")
|
||||||
|
h.log.Debug("Leaving hydra main loop")
|
||||||
|
}
|
5
initiator/initiator.go
Normal file
5
initiator/initiator.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package initiator
|
||||||
|
|
||||||
|
func New() {
|
||||||
|
|
||||||
|
}
|
10
initiator/libiscsi.go
Normal file
10
initiator/libiscsi.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package initiator
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is a straight set of bindings for the C "iscsi/iscsi.h" file
|
||||||
|
* We're working against 1.11.0 as that's what's in fedora 21, hurrah
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
|
33
iscsi-hydra.go
Normal file
33
iscsi-hydra.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import(
|
||||||
|
"os"
|
||||||
|
"net"
|
||||||
|
"ur.gs/go-iscsi/hydra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
config, err := hydra.ParseCommandLine(os.Args); if err != nil {
|
||||||
|
// We rely on hydra.ParseCommandLine() to output any error messages
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := config.Logger
|
||||||
|
|
||||||
|
logger.Info("Starting")
|
||||||
|
logger.Debug("Configuration: %+v", *config)
|
||||||
|
|
||||||
|
// Set up target listener
|
||||||
|
logger.Info("Listening on %s as target", config.TargetListen)
|
||||||
|
ln, err := net.Listen("tcp", config.TargetListen); if err != nil {
|
||||||
|
logger.Error("As target: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the hydra here
|
||||||
|
hydra.New(ln, config).Run()
|
||||||
|
|
||||||
|
logger.Info("Exiting")
|
||||||
|
ln.Close()
|
||||||
|
}
|
5
target/target.go
Normal file
5
target/target.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package target
|
||||||
|
|
||||||
|
func New() {
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user