Initial commit

main
Nick Thomas 8 years ago
commit 24a13ff5ac

@ -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:

@ -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
}

@ -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
}

@ -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")
}

@ -0,0 +1,5 @@
package initiator
func New() {
}

@ -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"

@ -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()
}

@ -0,0 +1,5 @@
package target
func New() {
}
Loading…
Cancel
Save