Initial commit
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…
Reference in New Issue