From 82e6d230a20ccad531eb6ccfdb400e52b1b8dee8 Mon Sep 17 00:00:00 2001 From: Le Zhang Date: Tue, 25 Oct 2016 10:34:13 +0800 Subject: [PATCH] PR implementation --- .../src/github.com/satori/go.uuid/.travis.yml | 21 + .../src/github.com/satori/go.uuid/LICENSE | 20 + .../src/github.com/satori/go.uuid/README.md | 65 ++ .../satori/go.uuid/benchmarks_test.go | 123 ++++ .../src/github.com/satori/go.uuid/uuid.go | 481 +++++++++++++ .../github.com/satori/go.uuid/uuid_test.go | 633 ++++++++++++++++++ pkg/api/types.go | 41 +- pkg/port/iscsit/conn.go | 2 +- pkg/port/iscsit/iscsid.go | 11 +- pkg/port/iscsit/iscsit.go | 27 +- pkg/port/iscsit/session.go | 25 +- pkg/scsi/cmd.go | 47 +- pkg/scsi/lun.go | 7 +- pkg/scsi/sbc.go | 37 +- pkg/scsi/scsi.go | 9 +- pkg/scsi/scsi_pr.go | 324 +++++++++ pkg/scsi/scsilumap.go | 7 + pkg/scsi/spc.go | 548 ++++++++++++++- pkg/scsi/target.go | 29 +- 19 files changed, 2382 insertions(+), 75 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/LICENSE create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/README.md create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/benchmarks_test.go create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/uuid.go create mode 100644 Godeps/_workspace/src/github.com/satori/go.uuid/uuid_test.go create mode 100644 pkg/scsi/scsi_pr.go diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/.travis.yml b/Godeps/_workspace/src/github.com/satori/go.uuid/.travis.yml new file mode 100644 index 0000000..bf90ad5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/.travis.yml @@ -0,0 +1,21 @@ +language: go +sudo: false +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - tip +matrix: + allow_failures: + - go: tip + fast_finish: true +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -service=travis-ci +notifications: + email: false diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/LICENSE b/Godeps/_workspace/src/github.com/satori/go.uuid/LICENSE new file mode 100644 index 0000000..488357b --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013-2016 by Maxim Bublis + +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. diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/README.md b/Godeps/_workspace/src/github.com/satori/go.uuid/README.md new file mode 100644 index 0000000..b6aad1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/README.md @@ -0,0 +1,65 @@ +# UUID package for Go language + +[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid) +[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) +[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid) + +This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. + +With 100% test coverage and benchmarks out of box. + +Supported versions: +* Version 1, based on timestamp and MAC address (RFC 4122) +* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) +* Version 3, based on MD5 hashing (RFC 4122) +* Version 4, based on random numbers (RFC 4122) +* Version 5, based on SHA-1 hashing (RFC 4122) + +## Installation + +Use the `go` command: + + $ go get github.com/satori/go.uuid + +## Requirements + +UUID package requires Go >= 1.2. + +## Example + +```go +package main + +import ( + "fmt" + "github.com/satori/go.uuid" +) + +func main() { + // Creating UUID Version 4 + u1 := uuid.NewV4() + fmt.Printf("UUIDv4: %s\n", u1) + + // Parsing UUID from string input + u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + fmt.Printf("Something gone wrong: %s", err) + } + fmt.Printf("Successfully parsed: %s", u2) +} +``` + +## Documentation + +[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project. + +## Links +* [RFC 4122](http://tools.ietf.org/html/rfc4122) +* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) + +## Copyright + +Copyright (C) 2013-2016 by Maxim Bublis . + +UUID package released under MIT License. +See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details. diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/benchmarks_test.go b/Godeps/_workspace/src/github.com/satori/go.uuid/benchmarks_test.go new file mode 100644 index 0000000..c3baeab --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/benchmarks_test.go @@ -0,0 +1,123 @@ +// Copyright (C) 2013-2015 by Maxim Bublis +// +// 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. + +package uuid + +import ( + "testing" +) + +func BenchmarkFromBytes(b *testing.B) { + bytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + for i := 0; i < b.N; i++ { + FromBytes(bytes) + } +} + +func BenchmarkFromString(b *testing.B) { + s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkFromStringUrn(b *testing.B) { + s := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkFromStringWithBrackets(b *testing.B) { + s := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" + for i := 0; i < b.N; i++ { + FromString(s) + } +} + +func BenchmarkNewV1(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV1() + } +} + +func BenchmarkNewV2(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV2(DomainPerson) + } +} + +func BenchmarkNewV3(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV3(NamespaceDNS, "www.example.com") + } +} + +func BenchmarkNewV4(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV4() + } +} + +func BenchmarkNewV5(b *testing.B) { + for i := 0; i < b.N; i++ { + NewV5(NamespaceDNS, "www.example.com") + } +} + +func BenchmarkMarshalBinary(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + u.MarshalBinary() + } +} + +func BenchmarkMarshalText(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + u.MarshalText() + } +} + +func BenchmarkUnmarshalBinary(b *testing.B) { + bytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + u := UUID{} + for i := 0; i < b.N; i++ { + u.UnmarshalBinary(bytes) + } +} + +func BenchmarkUnmarshalText(b *testing.B) { + bytes := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + u := UUID{} + for i := 0; i < b.N; i++ { + u.UnmarshalText(bytes) + } +} + +var sink string + +func BenchmarkMarshalToString(b *testing.B) { + u := NewV4() + for i := 0; i < b.N; i++ { + sink = u.String() + } +} diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/uuid.go b/Godeps/_workspace/src/github.com/satori/go.uuid/uuid.go new file mode 100644 index 0000000..295f3fc --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/uuid.go @@ -0,0 +1,481 @@ +// Copyright (C) 2013-2015 by Maxim Bublis +// +// 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. + +// Package uuid provides implementation of Universally Unique Identifier (UUID). +// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and +// version 2 (as specified in DCE 1.1). +package uuid + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "database/sql/driver" + "encoding/binary" + "encoding/hex" + "fmt" + "hash" + "net" + "os" + "sync" + "time" +) + +// UUID layout variants. +const ( + VariantNCS = iota + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// UUID DCE domains. +const ( + DomainPerson = iota + DomainGroup + DomainOrg +) + +// Difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). +const epochStart = 122192928000000000 + +// Used in string method conversion +const dash byte = '-' + +// UUID v1/v2 storage. +var ( + storageMutex sync.Mutex + storageOnce sync.Once + epochFunc = unixTimeFunc + clockSequence uint16 + lastTime uint64 + hardwareAddr [6]byte + posixUID = uint32(os.Getuid()) + posixGID = uint32(os.Getgid()) +) + +// String parse helpers. +var ( + urnPrefix = []byte("urn:uuid:") + byteGroups = []int{8, 4, 4, 4, 12} +) + +func initClockSequence() { + buf := make([]byte, 2) + safeRandom(buf) + clockSequence = binary.BigEndian.Uint16(buf) +} + +func initHardwareAddr() { + interfaces, err := net.Interfaces() + if err == nil { + for _, iface := range interfaces { + if len(iface.HardwareAddr) >= 6 { + copy(hardwareAddr[:], iface.HardwareAddr) + return + } + } + } + + // Initialize hardwareAddr randomly in case + // of real network interfaces absence + safeRandom(hardwareAddr[:]) + + // Set multicast bit as recommended in RFC 4122 + hardwareAddr[0] |= 0x01 +} + +func initStorage() { + initClockSequence() + initHardwareAddr() +} + +func safeRandom(dest []byte) { + if _, err := rand.Read(dest); err != nil { + panic(err) + } +} + +// Returns difference in 100-nanosecond intervals between +// UUID epoch (October 15, 1582) and current time. +// This is default epoch calculation function. +func unixTimeFunc() uint64 { + return epochStart + uint64(time.Now().UnixNano()/100) +} + +// UUID representation compliant with specification +// described in RFC 4122. +type UUID [16]byte + +// NullUUID can be used with the standard sql package to represent a +// UUID value that can be NULL in the database +type NullUUID struct { + UUID UUID + Valid bool +} + +// The nil UUID is special form of UUID that is specified to have all +// 128 bits set to zero. +var Nil = UUID{} + +// Predefined namespace UUIDs. +var ( + NamespaceDNS, _ = FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NamespaceURL, _ = FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NamespaceOID, _ = FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NamespaceX500, _ = FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8") +) + +// And returns result of binary AND of two UUIDs. +func And(u1 UUID, u2 UUID) UUID { + u := UUID{} + for i := 0; i < 16; i++ { + u[i] = u1[i] & u2[i] + } + return u +} + +// Or returns result of binary OR of two UUIDs. +func Or(u1 UUID, u2 UUID) UUID { + u := UUID{} + for i := 0; i < 16; i++ { + u[i] = u1[i] | u2[i] + } + return u +} + +// Equal returns true if u1 and u2 equals, otherwise returns false. +func Equal(u1 UUID, u2 UUID) bool { + return bytes.Equal(u1[:], u2[:]) +} + +// Version returns algorithm version used to generate UUID. +func (u UUID) Version() uint { + return uint(u[6] >> 4) +} + +// Variant returns UUID layout variant. +func (u UUID) Variant() uint { + switch { + case (u[8] & 0x80) == 0x00: + return VariantNCS + case (u[8]&0xc0)|0x80 == 0x80: + return VariantRFC4122 + case (u[8]&0xe0)|0xc0 == 0xc0: + return VariantMicrosoft + } + return VariantFuture +} + +// Bytes returns bytes slice representation of UUID. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Returns canonical string representation of UUID: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := make([]byte, 36) + + hex.Encode(buf[0:8], u[0:4]) + buf[8] = dash + hex.Encode(buf[9:13], u[4:6]) + buf[13] = dash + hex.Encode(buf[14:18], u[6:8]) + buf[18] = dash + hex.Encode(buf[19:23], u[8:10]) + buf[23] = dash + hex.Encode(buf[24:], u[10:]) + + return string(buf) +} + +// SetVersion sets version bits. +func (u *UUID) SetVersion(v byte) { + u[6] = (u[6] & 0x0f) | (v << 4) +} + +// SetVariant sets variant bits as described in RFC 4122. +func (u *UUID) SetVariant() { + u[8] = (u[8] & 0xbf) | 0x80 +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The encoding is the same as returned by String. +func (u UUID) MarshalText() (text []byte, err error) { + text = []byte(u.String()) + return +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Following formats are supported: +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" +func (u *UUID) UnmarshalText(text []byte) (err error) { + if len(text) < 32 { + err = fmt.Errorf("uuid: UUID string too short: %s", text) + return + } + + t := text[:] + braced := false + + if bytes.Equal(t[:9], urnPrefix) { + t = t[9:] + } else if t[0] == '{' { + braced = true + t = t[1:] + } + + b := u[:] + + for i, byteGroup := range byteGroups { + if i > 0 { + if t[0] != '-' { + err = fmt.Errorf("uuid: invalid string format") + return + } + t = t[1:] + } + + if len(t) < byteGroup { + err = fmt.Errorf("uuid: UUID string too short: %s", text) + return + } + + if i == 4 && len(t) > byteGroup && + ((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) { + err = fmt.Errorf("uuid: UUID string too long: %s", text) + return + } + + _, err = hex.Decode(b[:byteGroup/2], t[:byteGroup]) + if err != nil { + return + } + + t = t[byteGroup:] + b = b[byteGroup/2:] + } + + return +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (u UUID) MarshalBinary() (data []byte, err error) { + data = u.Bytes() + return +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// It will return error if the slice isn't 16 bytes long. +func (u *UUID) UnmarshalBinary(data []byte) (err error) { + if len(data) != 16 { + err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) + return + } + copy(u[:], data) + + return +} + +// Value implements the driver.Valuer interface. +func (u UUID) Value() (driver.Value, error) { + return u.String(), nil +} + +// Scan implements the sql.Scanner interface. +// A 16-byte slice is handled by UnmarshalBinary, while +// a longer byte slice or a string is handled by UnmarshalText. +func (u *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + if len(src) == 16 { + return u.UnmarshalBinary(src) + } + return u.UnmarshalText(src) + + case string: + return u.UnmarshalText([]byte(src)) + } + + return fmt.Errorf("uuid: cannot convert %T to UUID", src) +} + +// Value implements the driver.Valuer interface. +func (u NullUUID) Value() (driver.Value, error) { + if !u.Valid { + return nil, nil + } + // Delegate to UUID Value function + return u.UUID.Value() +} + +// Scan implements the sql.Scanner interface. +func (u *NullUUID) Scan(src interface{}) error { + if src == nil { + u.UUID, u.Valid = Nil, false + return nil + } + + // Delegate to UUID Scan function + u.Valid = true + return u.UUID.Scan(src) +} + +// FromBytes returns UUID converted from raw byte slice input. +// It will return error if the slice isn't 16 bytes long. +func FromBytes(input []byte) (u UUID, err error) { + err = u.UnmarshalBinary(input) + return +} + +// FromBytesOrNil returns UUID converted from raw byte slice input. +// Same behavior as FromBytes, but returns a Nil UUID on error. +func FromBytesOrNil(input []byte) UUID { + uuid, err := FromBytes(input) + if err != nil { + return Nil + } + return uuid +} + +// FromString returns UUID parsed from string input. +// Input is expected in a form accepted by UnmarshalText. +func FromString(input string) (u UUID, err error) { + err = u.UnmarshalText([]byte(input)) + return +} + +// FromStringOrNil returns UUID parsed from string input. +// Same behavior as FromString, but returns a Nil UUID on error. +func FromStringOrNil(input string) UUID { + uuid, err := FromString(input) + if err != nil { + return Nil + } + return uuid +} + +// Returns UUID v1/v2 storage state. +// Returns epoch timestamp, clock sequence, and hardware address. +func getStorage() (uint64, uint16, []byte) { + storageOnce.Do(initStorage) + + storageMutex.Lock() + defer storageMutex.Unlock() + + timeNow := epochFunc() + // Clock changed backwards since last UUID generation. + // Should increase clock sequence. + if timeNow <= lastTime { + clockSequence++ + } + lastTime = timeNow + + return timeNow, clockSequence, hardwareAddr[:] +} + +// NewV1 returns UUID based on current timestamp and MAC address. +func NewV1() UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := getStorage() + + binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + + copy(u[10:], hardwareAddr) + + u.SetVersion(1) + u.SetVariant() + + return u +} + +// NewV2 returns DCE Security UUID based on POSIX UID/GID. +func NewV2(domain byte) UUID { + u := UUID{} + + timeNow, clockSeq, hardwareAddr := getStorage() + + switch domain { + case DomainPerson: + binary.BigEndian.PutUint32(u[0:], posixUID) + case DomainGroup: + binary.BigEndian.PutUint32(u[0:], posixGID) + } + + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) + binary.BigEndian.PutUint16(u[8:], clockSeq) + u[9] = domain + + copy(u[10:], hardwareAddr) + + u.SetVersion(2) + u.SetVariant() + + return u +} + +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. +func NewV3(ns UUID, name string) UUID { + u := newFromHash(md5.New(), ns, name) + u.SetVersion(3) + u.SetVariant() + + return u +} + +// NewV4 returns random generated UUID. +func NewV4() UUID { + u := UUID{} + safeRandom(u[:]) + u.SetVersion(4) + u.SetVariant() + + return u +} + +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. +func NewV5(ns UUID, name string) UUID { + u := newFromHash(sha1.New(), ns, name) + u.SetVersion(5) + u.SetVariant() + + return u +} + +// Returns UUID based on hashing of namespace UUID and name. +func newFromHash(h hash.Hash, ns UUID, name string) UUID { + u := UUID{} + h.Write(ns[:]) + h.Write([]byte(name)) + copy(u[:], h.Sum(nil)) + + return u +} diff --git a/Godeps/_workspace/src/github.com/satori/go.uuid/uuid_test.go b/Godeps/_workspace/src/github.com/satori/go.uuid/uuid_test.go new file mode 100644 index 0000000..5650480 --- /dev/null +++ b/Godeps/_workspace/src/github.com/satori/go.uuid/uuid_test.go @@ -0,0 +1,633 @@ +// Copyright (C) 2013, 2015 by Maxim Bublis +// +// 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. + +package uuid + +import ( + "bytes" + "testing" +) + +func TestBytes(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + bytes1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + if !bytes.Equal(u.Bytes(), bytes1) { + t.Errorf("Incorrect bytes representation for UUID: %s", u) + } +} + +func TestString(t *testing.T) { + if NamespaceDNS.String() != "6ba7b810-9dad-11d1-80b4-00c04fd430c8" { + t.Errorf("Incorrect string representation for UUID: %s", NamespaceDNS.String()) + } +} + +func TestEqual(t *testing.T) { + if !Equal(NamespaceDNS, NamespaceDNS) { + t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceDNS) + } + + if Equal(NamespaceDNS, NamespaceURL) { + t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceURL) + } +} + +func TestOr(t *testing.T) { + u1 := UUID{0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff} + u2 := UUID{0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00} + + u := UUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + + if !Equal(u, Or(u1, u2)) { + t.Errorf("Incorrect bitwise OR result %s", Or(u1, u2)) + } +} + +func TestAnd(t *testing.T) { + u1 := UUID{0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff} + u2 := UUID{0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00} + + u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if !Equal(u, And(u1, u2)) { + t.Errorf("Incorrect bitwise AND result %s", And(u1, u2)) + } +} + +func TestVersion(t *testing.T) { + u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u.Version() != 1 { + t.Errorf("Incorrect version for UUID: %d", u.Version()) + } +} + +func TestSetVersion(t *testing.T) { + u := UUID{} + u.SetVersion(4) + + if u.Version() != 4 { + t.Errorf("Incorrect version for UUID after u.setVersion(4): %d", u.Version()) + } +} + +func TestVariant(t *testing.T) { + u1 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u1.Variant() != VariantNCS { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantNCS, u1.Variant()) + } + + u2 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u2.Variant() != VariantRFC4122 { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantRFC4122, u2.Variant()) + } + + u3 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u3.Variant() != VariantMicrosoft { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantMicrosoft, u3.Variant()) + } + + u4 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + if u4.Variant() != VariantFuture { + t.Errorf("Incorrect variant for UUID variant %d: %d", VariantFuture, u4.Variant()) + } +} + +func TestSetVariant(t *testing.T) { + u := new(UUID) + u.SetVariant() + + if u.Variant() != VariantRFC4122 { + t.Errorf("Incorrect variant for UUID after u.setVariant(): %d", u.Variant()) + } +} + +func TestFromBytes(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1, err := FromBytes(b1) + if err != nil { + t.Errorf("Error parsing UUID from bytes: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + + _, err = FromBytes(b2) + if err == nil { + t.Errorf("Should return error parsing from empty byte slice, got %s", err) + } +} + +func TestMarshalBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + b2, err := u.MarshalBinary() + if err != nil { + t.Errorf("Error marshaling UUID: %s", err) + } + + if !bytes.Equal(b1, b2) { + t.Errorf("Marshaled UUID should be %s, got %s", b1, b2) + } +} + +func TestUnmarshalBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1 := UUID{} + err := u1.UnmarshalBinary(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + u2 := UUID{} + + err = u2.UnmarshalBinary(b2) + if err == nil { + t.Errorf("Should return error unmarshalling from empty byte slice, got %s", err) + } +} + +func TestFromString(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + s2 := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" + s3 := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + _, err := FromString("") + if err == nil { + t.Errorf("Should return error trying to parse empty string, got %s", err) + } + + u1, err := FromString(s1) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + u2, err := FromString(s2) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u2) { + t.Errorf("UUIDs should be equal: %s and %s", u, u2) + } + + u3, err := FromString(s3) + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + if !Equal(u, u3) { + t.Errorf("UUIDs should be equal: %s and %s", u, u3) + } +} + +func TestFromStringShort(t *testing.T) { + // Invalid 35-character UUID string + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c" + + for i := len(s1); i >= 0; i-- { + _, err := FromString(s1[:i]) + if err == nil { + t.Errorf("Should return error trying to parse too short string, got %s", err) + } + } +} + +func TestFromStringLong(t *testing.T) { + // Invalid 37+ character UUID string + s := []string{ + "6ba7b810-9dad-11d1-80b4-00c04fd430c8=", + "6ba7b810-9dad-11d1-80b4-00c04fd430c8}", + "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}f", + "6ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8", + } + + for _, str := range s { + _, err := FromString(str) + if err == nil { + t.Errorf("Should return error trying to parse too long string, passed %s", str) + } + } +} + +func TestFromStringInvalid(t *testing.T) { + // Invalid UUID string formats + s := []string{ + "6ba7b8109dad11d180b400c04fd430c8", + "6ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8", + "urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", + "6ba7b8109-dad-11d1-80b4-00c04fd430c8", + "6ba7b810-9dad1-1d1-80b4-00c04fd430c8", + "6ba7b810-9dad-11d18-0b4-00c04fd430c8", + "6ba7b810-9dad-11d1-80b40-0c04fd430c8", + "6ba7b810+9dad+11d1+80b4+00c04fd430c8", + "6ba7b810-9dad11d180b400c04fd430c8", + "6ba7b8109dad-11d180b400c04fd430c8", + "6ba7b8109dad11d1-80b400c04fd430c8", + "6ba7b8109dad11d180b4-00c04fd430c8", + } + + for _, str := range s { + _, err := FromString(str) + if err == nil { + t.Errorf("Should return error trying to parse invalid string, passed %s", str) + } + } +} + +func TestFromStringOrNil(t *testing.T) { + u := FromStringOrNil("") + if u != Nil { + t.Errorf("Should return Nil UUID on parse failure, got %s", u) + } +} + +func TestFromBytesOrNil(t *testing.T) { + b := []byte{} + u := FromBytesOrNil(b) + if u != Nil { + t.Errorf("Should return Nil UUID on parse failure, got %s", u) + } +} + +func TestMarshalText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + b2, err := u.MarshalText() + if err != nil { + t.Errorf("Error marshaling UUID: %s", err) + } + + if !bytes.Equal(b1, b2) { + t.Errorf("Marshaled UUID should be %s, got %s", b1, b2) + } +} + +func TestUnmarshalText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + u1 := UUID{} + err := u1.UnmarshalText(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte("") + u2 := UUID{} + + err = u2.UnmarshalText(b2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestValue(t *testing.T) { + u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + if err != nil { + t.Errorf("Error parsing UUID from string: %s", err) + } + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != u.String() { + t.Errorf("Wrong value returned, should be equal: %s and %s", val, u) + } +} + +func TestValueNil(t *testing.T) { + u := UUID{} + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != Nil.String() { + t.Errorf("Wrong value returned, should be equal to UUID.Nil: %s", val) + } +} + +func TestNullUUIDValueNil(t *testing.T) { + u := NullUUID{} + + val, err := u.Value() + if err != nil { + t.Errorf("Error getting UUID value: %s", err) + } + + if val != nil { + t.Errorf("Wrong value returned, should be nil: %s", val) + } +} + +func TestScanBinary(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + u1 := UUID{} + err := u1.Scan(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte{} + u2 := UUID{} + + err = u2.Scan(b2) + if err == nil { + t.Errorf("Should return error unmarshalling from empty byte slice, got %s", err) + } +} + +func TestScanString(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + u1 := UUID{} + err := u1.Scan(s1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + s2 := "" + u2 := UUID{} + + err = u2.Scan(s2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestScanText(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + u1 := UUID{} + err := u1.Scan(b1) + if err != nil { + t.Errorf("Error unmarshaling UUID: %s", err) + } + + if !Equal(u, u1) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1) + } + + b2 := []byte("") + u2 := UUID{} + + err = u2.Scan(b2) + if err == nil { + t.Errorf("Should return error trying to unmarshal from empty string") + } +} + +func TestScanUnsupported(t *testing.T) { + u := UUID{} + + err := u.Scan(true) + if err == nil { + t.Errorf("Should return error trying to unmarshal from bool") + } +} + +func TestScanNil(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + + err := u.Scan(nil) + if err == nil { + t.Errorf("Error UUID shouldn't allow unmarshalling from nil") + } +} + +func TestNullUUIDScanValid(t *testing.T) { + u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + u1 := NullUUID{} + err := u1.Scan(s1) + if err != nil { + t.Errorf("Error unmarshaling NullUUID: %s", err) + } + + if !u1.Valid { + t.Errorf("NullUUID should be valid") + } + + if !Equal(u, u1.UUID) { + t.Errorf("UUIDs should be equal: %s and %s", u, u1.UUID) + } +} + +func TestNullUUIDScanNil(t *testing.T) { + u := NullUUID{UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}, true} + + err := u.Scan(nil) + if err != nil { + t.Errorf("Error unmarshaling NullUUID: %s", err) + } + + if u.Valid { + t.Errorf("NullUUID should not be valid") + } + + if !Equal(u.UUID, Nil) { + t.Errorf("NullUUID value should be equal to Nil: %v", u) + } +} + +func TestNewV1(t *testing.T) { + u := NewV1() + + if u.Version() != 1 { + t.Errorf("UUIDv1 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv1 generated with incorrect variant: %d", u.Variant()) + } + + u1 := NewV1() + u2 := NewV1() + + if Equal(u1, u2) { + t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u1, u2) + } + + oldFunc := epochFunc + epochFunc = func() uint64 { return 0 } + + u3 := NewV1() + u4 := NewV1() + + if Equal(u3, u4) { + t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u3, u4) + } + + epochFunc = oldFunc +} + +func TestNewV2(t *testing.T) { + u1 := NewV2(DomainPerson) + + if u1.Version() != 2 { + t.Errorf("UUIDv2 generated with incorrect version: %d", u1.Version()) + } + + if u1.Variant() != VariantRFC4122 { + t.Errorf("UUIDv2 generated with incorrect variant: %d", u1.Variant()) + } + + u2 := NewV2(DomainGroup) + + if u2.Version() != 2 { + t.Errorf("UUIDv2 generated with incorrect version: %d", u2.Version()) + } + + if u2.Variant() != VariantRFC4122 { + t.Errorf("UUIDv2 generated with incorrect variant: %d", u2.Variant()) + } +} + +func TestNewV3(t *testing.T) { + u := NewV3(NamespaceDNS, "www.example.com") + + if u.Version() != 3 { + t.Errorf("UUIDv3 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv3 generated with incorrect variant: %d", u.Variant()) + } + + if u.String() != "5df41881-3aed-3515-88a7-2f4a814cf09e" { + t.Errorf("UUIDv3 generated incorrectly: %s", u.String()) + } + + u = NewV3(NamespaceDNS, "python.org") + + if u.String() != "6fa459ea-ee8a-3ca4-894e-db77e160355e" { + t.Errorf("UUIDv3 generated incorrectly: %s", u.String()) + } + + u1 := NewV3(NamespaceDNS, "golang.org") + u2 := NewV3(NamespaceDNS, "golang.org") + if !Equal(u1, u2) { + t.Errorf("UUIDv3 generated different UUIDs for same namespace and name: %s and %s", u1, u2) + } + + u3 := NewV3(NamespaceDNS, "example.com") + if Equal(u1, u3) { + t.Errorf("UUIDv3 generated same UUIDs for different names in same namespace: %s and %s", u1, u2) + } + + u4 := NewV3(NamespaceURL, "golang.org") + if Equal(u1, u4) { + t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4) + } +} + +func TestNewV4(t *testing.T) { + u := NewV4() + + if u.Version() != 4 { + t.Errorf("UUIDv4 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv4 generated with incorrect variant: %d", u.Variant()) + } +} + +func TestNewV5(t *testing.T) { + u := NewV5(NamespaceDNS, "www.example.com") + + if u.Version() != 5 { + t.Errorf("UUIDv5 generated with incorrect version: %d", u.Version()) + } + + if u.Variant() != VariantRFC4122 { + t.Errorf("UUIDv5 generated with incorrect variant: %d", u.Variant()) + } + + u = NewV5(NamespaceDNS, "python.org") + + if u.String() != "886313e1-3b8a-5372-9b90-0c9aee199e5d" { + t.Errorf("UUIDv5 generated incorrectly: %s", u.String()) + } + + u1 := NewV5(NamespaceDNS, "golang.org") + u2 := NewV5(NamespaceDNS, "golang.org") + if !Equal(u1, u2) { + t.Errorf("UUIDv5 generated different UUIDs for same namespace and name: %s and %s", u1, u2) + } + + u3 := NewV5(NamespaceDNS, "example.com") + if Equal(u1, u3) { + t.Errorf("UUIDv5 generated same UUIDs for different names in same namespace: %s and %s", u1, u2) + } + + u4 := NewV5(NamespaceURL, "golang.org") + if Equal(u1, u4) { + t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4) + } +} diff --git a/pkg/api/types.go b/pkg/api/types.go index a8fec09..ce41f00 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -18,6 +18,9 @@ package api import ( "bytes" "errors" + "sync" + + "github.com/satori/go.uuid" ) type SCSICommandType byte @@ -158,7 +161,7 @@ type SCSICommand struct { OutSDBBuffer SCSIDataBuffer RelTargetPortID uint16 // Command ITN ID - CommandITNID uint64 + ITNexusID uuid.UUID Offset uint64 TL uint32 SCB *bytes.Buffer @@ -174,12 +177,8 @@ type SCSICommand struct { } type ITNexus struct { - ID uint64 `json:"id"` - Ctime uint64 `json:"ctime"` - Commands []SCSICommand `json:"-"` - Target *SCSITarget `json:"-"` - Host int `json:"host"` - Info string `json:"info"` + ID uuid.UUID `json:"id"` /*UUIDv1*/ + Tag string `json:"Tag"` /*For protocal spec identifer*/ } type ITNexusLuInfo struct { @@ -199,15 +198,18 @@ type TargetPortGroup struct { } type SCSITarget struct { - Name string `json:"name"` - TID int `json:"tid"` - LID int `json:"lid"` - State SCSITargetState `json:"state"` - Devices LUNMap `json:"-"` - LUN0 *SCSILu `json:"-"` - ITNexus []*ITNexus `json:"itnexus"` + Name string `json:"name"` + TID int `json:"tid"` + LID int `json:"lid"` + State SCSITargetState `json:"state"` + Devices LUNMap `json:"-"` + LUN0 *SCSILu `json:"-"` + TargetPortGroups []*TargetPortGroup `json:"tpg"` SCSITargetDriver interface{} `json:"-"` + + ITNexusMutex sync.Mutex + ITNexus map[uuid.UUID]*ITNexus `json:"itnexus"` } type SCSITargetDriverState int @@ -336,6 +338,7 @@ type BackingStore interface { type SCSIDeviceProtocol interface { PerformCommand(opcode int) interface{} + PerformServiceAction(opcode int, action uint8) interface{} InitLu(lu *SCSILu) error ConfigLu(lu *SCSILu) error OnlineLu(lu *SCSILu) error @@ -348,6 +351,14 @@ type ModePage struct { Data []byte // Rest of mode page info } +type SCSIReservation struct { + ID uuid.UUID //Internal Reservation ID + Key uint64 + ITNexusID uuid.UUID + Scope uint8 + Type uint8 +} + type SCSILu struct { Address uint64 Size uint64 @@ -355,7 +366,7 @@ type SCSILu struct { Path string BsoFlags int BlockShift uint - ReserveID uint64 + ReserveID uuid.UUID Attrs SCSILuPhyAttribute ModePages []ModePage Storage BackingStore diff --git a/pkg/port/iscsit/conn.go b/pkg/port/iscsit/conn.go index 1103f08..e607f15 100644 --- a/pkg/port/iscsit/conn.go +++ b/pkg/port/iscsit/conn.go @@ -54,7 +54,7 @@ type iscsiConnection struct { sessionType int sessionParam []ISCSISessionParam tid int - cid uint16 + CID uint16 rxIOState int txIOState int refcount int diff --git a/pkg/port/iscsit/iscsid.go b/pkg/port/iscsit/iscsid.go index 3def0ff..f0081f8 100644 --- a/pkg/port/iscsit/iscsid.go +++ b/pkg/port/iscsit/iscsid.go @@ -30,6 +30,7 @@ import ( "github.com/gostor/gotgt/pkg/port" "github.com/gostor/gotgt/pkg/scsi" "github.com/gostor/gotgt/pkg/util" + "github.com/satori/go.uuid" ) type ISCSITargetService struct { @@ -165,7 +166,7 @@ func (s *ISCSITargetService) handler(events byte, conn *iscsiConnection) { s.txHandler(conn) } if conn.state == CONN_STATE_CLOSE { - glog.Warningf("iscsi connection[%d] closed", conn.cid) + glog.Warningf("iscsi connection[%d] closed", conn.CID) conn.close() } } @@ -324,6 +325,7 @@ func (s *ISCSITargetService) iscsiExecLogin(conn *iscsiConnection) error { {"DataSequenceInOrder", "Yes"}, }), } + conn.CID = cmd.ConnID pairs := util.ParseKVText(cmd.RawData) if initiatorName, ok := pairs["InitiatorName"]; ok { conn.initiator = initiatorName @@ -376,11 +378,14 @@ func (s *ISCSITargetService) iscsiExecLogin(conn *iscsiConnection) error { case SESSION_NORMAL: if conn.session == nil { // create a new session - sess, err := s.NewISCSISession(conn) + sess, err := s.NewISCSISession(conn, cmd.ISID) if err != nil { glog.Error(err) return err } + itnexus := &api.ITNexus{uuid.NewV1(), GeniSCSIITNexusID(sess)} + scsi.AddITNexus(&sess.Target.SCSITarget, itnexus) + sess.ITNexusID = itnexus.ID conn.session = sess } case SESSION_DISCOVERY: @@ -804,7 +809,7 @@ func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error { task.scmd.Direction = api.SCSIDataWrite } } - task.scmd.CommandITNID = task.conn.session.Tsih + task.scmd.ITNexusID = task.conn.session.ITNexusID task.scmd.SCB = bytes.NewBuffer(cmd.CDB) task.scmd.SCBLength = len(cmd.CDB) task.scmd.Lun = cmd.LUN diff --git a/pkg/port/iscsit/iscsit.go b/pkg/port/iscsit/iscsit.go index acab8c7..8bfb131 100644 --- a/pkg/port/iscsit/iscsit.go +++ b/pkg/port/iscsit/iscsit.go @@ -78,8 +78,8 @@ type iSCSITPGT struct { type ISCSITarget struct { api.SCSITarget api.SCSITargetDriverCommon - TPGTs map[uint16]*iSCSITPGT - Sessions []*ISCSISession + TPGTs map[uint16]*iSCSITPGT /* Key is a TPGT number */ + Sessions map[uint16]*ISCSISession /* Key is an TSIH */ SessionParam []ISCSISessionParam Alias string MaxSessions int @@ -87,6 +87,23 @@ type ISCSITarget struct { Rdma int NopInterval int NopCount int + TSIHCounter uint16 +} + +/* + * RFC 3720 iSCSI SSID = ISID , TPGT + * ISID is an 6 bytes number, TPGT is an 2 bytes number + * We combime ISID and TPGT together to be SSID + */ +func MakeSSID(ISID uint64, TPGT uint16) uint64 { + SSID := ISID<<16 | uint64(TPGT) + return SSID +} + +func ParseSSID(SSID uint64) (uint64, uint16) { + TPGT := uint16(uint64(0xFFFF) & SSID) + ISID := SSID >> 16 + return ISID, TPGT } func (tgt *ISCSITarget) FindTPG(portal string) (uint16, error) { @@ -103,8 +120,10 @@ func (tgt *ISCSITarget) FindTPG(portal string) (uint16, error) { func newISCSITarget(target *api.SCSITarget) *ISCSITarget { return &ISCSITarget{ - SCSITarget: *target, - TPGTs: make(map[uint16]*iSCSITPGT), + SCSITarget: *target, + TPGTs: make(map[uint16]*iSCSITPGT), + Sessions: make(map[uint16]*ISCSISession), + TSIHCounter: 1, } } diff --git a/pkg/port/iscsit/session.go b/pkg/port/iscsit/session.go index 6eaede5..8cc2cf2 100644 --- a/pkg/port/iscsit/session.go +++ b/pkg/port/iscsit/session.go @@ -20,6 +20,8 @@ import ( "fmt" "math/rand" "time" + + "github.com/satori/go.uuid" ) var ( @@ -149,8 +151,9 @@ type ISCSISession struct { Initiator string InitiatorAlias string Target *ISCSITarget - Isid uint64 - Tsih uint64 + ISID uint64 + TSIH uint64 + ITNexusID uuid.UUID ExpCmdSN uint32 // only one connection per session @@ -215,7 +218,7 @@ type iscsiPdu struct { } // New creates a new session. -func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISession, error) { +func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection, isid uint64) (*ISCSISession, error) { var ( target *ISCSITarget tsih uint64 @@ -235,7 +238,7 @@ func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISessi rand.Seed(int64(time.Now().UTC().Nanosecond())) tsih = uint64(rand.Uint32()) for _, s := range target.Sessions { - if s.Tsih == tsih { + if s.TSIH == tsih { tsih = 0 break } @@ -246,7 +249,8 @@ func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISessi } sess := &ISCSISession{ - Tsih: tsih, + TSIH: tsih, + ISID: isid, Initiator: conn.initiator, InitiatorAlias: conn.initiatorAlias, Target: target, @@ -259,3 +263,14 @@ func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISessi conn.session = sess return sess, nil } + +/* + * iSCSI I_T nexus identifer = (iSCSI Initiator Name + 'i' + ISID, iSCSI Target Name + 't' + Portal Group Tag) + */ +func GeniSCSIITNexusID(sess *ISCSISession) string { + strID := fmt.Sprintf("%si0x%12x,%st%d", + sess.Initiator, sess.ISID, + sess.Target.SCSITarget.Name, + sess.Connections[0].tpgt) + return strID +} diff --git a/pkg/scsi/cmd.go b/pkg/scsi/cmd.go index a385ba6..c2d6206 100644 --- a/pkg/scsi/cmd.go +++ b/pkg/scsi/cmd.go @@ -20,36 +20,33 @@ import ( "github.com/gostor/gotgt/pkg/util" ) -type SCSIPRServiceAction byte -type SCSIPRType byte +const ( + /* PERSISTENT_RESERVE_IN service action codes */ + PR_IN_READ_KEYS byte = 0x00 + PR_IN_READ_RESERVATION byte = 0x01 + PR_IN_REPORT_CAPABILITIES byte = 0x02 + PR_IN_READ_FULL_STATUS byte = 0x03 -var ( - // PERSISTENT_RESERVE_IN service action codes - PR_IN_READ_KEYS SCSIPRServiceAction = 0x00 - PR_IN_READ_RESERVATION SCSIPRServiceAction = 0x01 - PR_IN_REPORT_CAPABILITIES SCSIPRServiceAction = 0x02 - PR_IN_READ_FULL_STATUS SCSIPRServiceAction = 0x03 - - // PERSISTENT_RESERVE_OUT service action codes - PR_OUT_REGISTER SCSIPRServiceAction = 0x00 - PR_OUT_RESERVE SCSIPRServiceAction = 0x01 - PR_OUT_RELEASE SCSIPRServiceAction = 0x02 - PR_OUT_CLEAR SCSIPRServiceAction = 0x03 - PR_OUT_PREEMPT SCSIPRServiceAction = 0x04 - PR_OUT_PREEMPT_AND_ABORT SCSIPRServiceAction = 0x05 - PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY SCSIPRServiceAction = 0x06 - PR_OUT_REGISTER_AND_MOVE SCSIPRServiceAction = 0x07 + /* PERSISTENT_RESERVE_OUT service action codes */ + PR_OUT_REGISTER byte = 0x00 + PR_OUT_RESERVE byte = 0x01 + PR_OUT_RELEASE byte = 0x02 + PR_OUT_CLEAR byte = 0x03 + PR_OUT_PREEMPT byte = 0x04 + PR_OUT_PREEMPT_AND_ABORT byte = 0x05 + PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY byte = 0x06 + PR_OUT_REGISTER_AND_MOVE byte = 0x07 // Persistent Reservation scope PR_LU_SCOPE byte = 0x00 - // Persistent Reservation Type Mask format - PR_TYPE_WRITE_EXCLUSIVE SCSIPRType = 0x01 - PR_TYPE_EXCLUSIVE_ACCESS SCSIPRType = 0x03 - PR_TYPE_WRITE_EXCLUSIVE_REGONLY SCSIPRType = 0x05 - PR_TYPE_EXCLUSIVE_ACCESS_REGONLY SCSIPRType = 0x06 - PR_TYPE_WRITE_EXCLUSIVE_ALLREG SCSIPRType = 0x07 - PR_TYPE_EXCLUSIVE_ACCESS_ALLREG SCSIPRType = 0x08 + /* Persistent Reservation Type Mask format */ + PR_TYPE_WRITE_EXCLUSIVE byte = 0x01 + PR_TYPE_EXCLUSIVE_ACCESS byte = 0x03 + PR_TYPE_WRITE_EXCLUSIVE_REGONLY byte = 0x05 + PR_TYPE_EXCLUSIVE_ACCESS_REGONLY byte = 0x06 + PR_TYPE_WRITE_EXCLUSIVE_ALLREG byte = 0x07 + PR_TYPE_EXCLUSIVE_ACCESS_ALLREG byte = 0x08 ) const ( diff --git a/pkg/scsi/lun.go b/pkg/scsi/lun.go index 0212ecd..efcd88c 100644 --- a/pkg/scsi/lun.go +++ b/pkg/scsi/lun.go @@ -11,8 +11,7 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. -*/ +limitations under the License. */ package scsi @@ -80,6 +79,10 @@ func NewLUN0() *api.SCSILu { return lu } +func GetReservation(dev *api.SCSILu, nexusID uint64) *api.SCSIReservation { + return nil +} + func luPerformCommand(tid int, cmd *api.SCSICommand) api.SAMStat { op := int(cmd.SCB.Bytes()[0]) fn := cmd.Device.DeviceProtocol.PerformCommand(op) diff --git a/pkg/scsi/sbc.go b/pkg/scsi/sbc.go index f9c68b1..74c07e2 100644 --- a/pkg/scsi/sbc.go +++ b/pkg/scsi/sbc.go @@ -45,6 +45,16 @@ func (sbc SBCSCSIDeviceProtocol) PerformCommand(opcode int) interface{} { return sbc.SCSIDeviceOps[opcode] } +func (sbc SBCSCSIDeviceProtocol) PerformServiceAction(opcode int, action uint8) interface{} { + var sa *SCSIServiceAction + for _, sa = range sbc.SCSIDeviceOps[opcode].ServiceAction { + if sa.ServiceAction == action { + return sa + } + } + return nil +} + func (sbc SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error { // init LU's phy attribute lu.Attrs.DeviceType = sbc.DeviceType @@ -158,8 +168,23 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol { sbc.SCSIDeviceOps[api.MODE_SELECT_10] = NewSCSIDeviceOperation(SBCModeSelect, nil, PR_WE_FA|PR_EA_FA|PR_EA_FN|PR_WE_FN) sbc.SCSIDeviceOps[api.MODE_SENSE_10] = NewSCSIDeviceOperation(SBCModeSense, nil, PR_WE_FA|PR_WE_FN|PR_EA_FA|PR_EA_FN) - sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_IN] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0) - sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_OUT] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0) + + sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{ + {ServiceAction: PR_IN_READ_KEYS, CommandPerformFunc: SPCPRReadKeys}, + {ServiceAction: PR_IN_READ_RESERVATION, CommandPerformFunc: SPCPRReadReservation}, + {ServiceAction: PR_IN_REPORT_CAPABILITIES, CommandPerformFunc: SPCPRReportCapabilities}, + }, 0) + + sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_OUT] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{ + {ServiceAction: PR_OUT_REGISTER, CommandPerformFunc: SPCPRRegister}, + {ServiceAction: PR_OUT_RESERVE, CommandPerformFunc: SPCPRReserve}, + {ServiceAction: PR_OUT_RELEASE, CommandPerformFunc: SPCPRRelease}, + {ServiceAction: PR_OUT_CLEAR, CommandPerformFunc: SPCPRClear}, + {ServiceAction: PR_OUT_PREEMPT, CommandPerformFunc: SPCPRPreempt}, + // {ServiceAction: PR_OUT_PREEMPT_AND_ABORT, CommandPerformFunc: SPCPRPreempt}, + {ServiceAction: PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY, CommandPerformFunc: SPCPRRegister}, + {ServiceAction: PR_OUT_REGISTER_AND_MOVE, CommandPerformFunc: SPCPRRegisterAndMove}, + }, 0) sbc.SCSIDeviceOps[api.READ_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN) sbc.SCSIDeviceOps[api.WRITE_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN|PR_WE_FA|PR_WE_FN) @@ -173,8 +198,10 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol { sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SBCServiceAction, nil, 0) sbc.SCSIDeviceOps[api.REPORT_LUNS] = NewSCSIDeviceOperation(SPCReportLuns, nil, 0) - sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, nil, 0) - sbc.SCSIDeviceOps[api.EXCHANGE_MEDIUM] = NewSCSIDeviceOperation(SPCServiceAction, nil, 0) + sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{ + {ServiceAction: 0x0C, CommandPerformFunc: SPCReportSupportedOperationCodes}, + }, 0) + sbc.SCSIDeviceOps[api.EXCHANGE_MEDIUM] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0) sbc.SCSIDeviceOps[api.READ_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN) sbc.SCSIDeviceOps[api.WRITE_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_WE_FA|PR_WE_FN) sbc.SCSIDeviceOps[api.WRITE_VERIFY_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN) @@ -431,7 +458,7 @@ func SBCReserve(host int, cmd *api.SCSICommand) api.SAMStat { func SBCRelease(host int, cmd *api.SCSICommand) api.SAMStat { lun := *(*uint64)(unsafe.Pointer(&cmd.Lun)) - if err := deviceRelease(cmd.Target.TID, cmd.CommandITNID, lun, false); err != nil { + if err := deviceRelease(cmd.Target.TID, cmd.ITNexusID, lun, false); err != nil { return api.SAMStatReservationConflict } diff --git a/pkg/scsi/scsi.go b/pkg/scsi/scsi.go index b1f79f3..201ec72 100644 --- a/pkg/scsi/scsi.go +++ b/pkg/scsi/scsi.go @@ -25,6 +25,7 @@ import ( "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" + "github.com/satori/go.uuid" ) type SCSITargetService struct { @@ -70,7 +71,7 @@ func (s *SCSITargetService) AddCommandQueue(tid int, scmd *api.SCSICommand) erro } scmd.Target = target for _, it := range target.ITNexus { - if it.ID == scmd.CommandITNID { + if uuid.Equal(it.ID, scmd.ITNexusID) { itn = it break } @@ -99,13 +100,13 @@ func (s *SCSITargetService) AddCommandQueue(tid int, scmd *api.SCSICommand) erro } type SCSIServiceAction struct { - ServiceAction uint32 + ServiceAction uint8 CommandPerformFunc api.CommandFunc } type SCSIDeviceOperation struct { CommandPerformFunc api.CommandFunc - ServiceAction *SCSIServiceAction + ServiceAction []*SCSIServiceAction PRConflictBits uint8 } @@ -114,7 +115,7 @@ type BaseSCSIDeviceProtocol struct { SCSIDeviceOps []SCSIDeviceOperation } -func NewSCSIDeviceOperation(fn api.CommandFunc, sa *SCSIServiceAction, pr uint8) SCSIDeviceOperation { +func NewSCSIDeviceOperation(fn api.CommandFunc, sa []*SCSIServiceAction, pr uint8) SCSIDeviceOperation { return SCSIDeviceOperation{ CommandPerformFunc: fn, ServiceAction: sa, diff --git a/pkg/scsi/scsi_pr.go b/pkg/scsi/scsi_pr.go new file mode 100644 index 0000000..01749e0 --- /dev/null +++ b/pkg/scsi/scsi_pr.go @@ -0,0 +1,324 @@ +package scsi + +import ( + "github.com/gostor/gotgt/pkg/api" + "github.com/satori/go.uuid" +) + +type SCSIReservationOperator interface { + GetCurrentReservation(tgtName string, devUUID uint64) *api.SCSIReservation + SetCurrentReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool + IsCurrentReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool + + GetPRGeneration(tgtName string, devUUID uint64) (uint32, bool) + IncPRGeneration(tgtName string, devUUID uint64) bool + + AddReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool + GetReservation(tgtName string, devUUID uint64, ITNexusID uuid.UUID) *api.SCSIReservation + GetReservationList(tgtName string, devUUID uint64) []*api.SCSIReservation + DeleteAndRemoveReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) + RemoveReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) + RemoveAllReservation(tgtName string, devUUID uint64) + + IsKeyExists(tgtName string, devUUID uint64, key uint64) bool + + Save(tgtName string, devUUID uint64) bool +} + +var onePROperator SCSIReservationOperator + +func GetSCSIReservationOperator() SCSIReservationOperator { + if onePROperator == nil { + onePROperator = &SCSISimpleReservationOperator{ + targetReservations: make(map[string]SCSILUReservationMap), + } + } + return onePROperator +} + +type SCSILUReservation struct { + TargetName string + DeviceUUID uint64 + PRGeneration uint32 + Reservations []*api.SCSIReservation + CurrentReservation *api.SCSIReservation +} + +type SCSILUReservationMap map[uint64]*SCSILUReservation /* device uuid as the key */ + +type SCSISimpleReservationOperator struct { + SCSIReservationOperator + targetReservations map[string]SCSILUReservationMap /* target name as the key*/ +} + +func (op *SCSISimpleReservationOperator) InitLUReservation(tgtName string, devUUID uint64) { + var ( + targetRes SCSILUReservationMap + ok bool + ) + + if targetRes, ok = op.targetReservations[tgtName]; !ok { + op.targetReservations[tgtName] = make(SCSILUReservationMap) + targetRes = op.targetReservations[tgtName] + } + if _, ok = targetRes[devUUID]; !ok { + targetRes[devUUID] = &SCSILUReservation{TargetName: tgtName, + DeviceUUID: devUUID, + PRGeneration: 0, + Reservations: []*api.SCSIReservation{}, + CurrentReservation: nil, + } + } +} + +func (op *SCSISimpleReservationOperator) GetReservation(tgtName string, devUUID uint64, ITNexusID uuid.UUID) *api.SCSIReservation { + var ( + LURes *SCSILUReservation + SCSIRes *api.SCSIReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return nil + } + if LURes, ok = targetRes[devUUID]; !ok { + return nil + } + for _, SCSIRes = range LURes.Reservations { + if uuid.Equal(SCSIRes.ITNexusID, ITNexusID) { + return SCSIRes + } + } + + return nil +} + +func (op *SCSISimpleReservationOperator) GetPRGeneration(tgtName string, devUUID uint64) (uint32, bool) { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return 0, false + } + if LURes, ok = targetRes[devUUID]; !ok { + return 0, false + } + + return LURes.PRGeneration, true +} + +func (op *SCSISimpleReservationOperator) IncPRGeneration(tgtName string, devUUID uint64) bool { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return false + } + if LURes, ok = targetRes[devUUID]; !ok { + return false + } + LURes.PRGeneration++ + return true +} + +func (op *SCSISimpleReservationOperator) GetCurrentReservation(tgtName string, devUUID uint64) *api.SCSIReservation { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return nil + } + if LURes, ok = targetRes[devUUID]; !ok { + return nil + } + return LURes.CurrentReservation +} + +func (op *SCSISimpleReservationOperator) SetCurrentReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return false + } + if LURes, ok = targetRes[devUUID]; !ok { + return false + } + LURes.CurrentReservation = res + return true +} + +func (op *SCSISimpleReservationOperator) GetReservationList(tgtName string, devUUID uint64) []*api.SCSIReservation { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return nil + } + if LURes, ok = targetRes[devUUID]; !ok { + return nil + } + return LURes.Reservations +} + +func (op *SCSISimpleReservationOperator) DeleteAndRemoveReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) { + var ( + i int = -1 + ok bool + tmpRes *api.SCSIReservation + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return + } + if LURes, ok = targetRes[devUUID]; !ok { + return + } + + resArray := LURes.Reservations + curRes := LURes.CurrentReservation + + for i, tmpRes = range resArray { + if tmpRes == res { + break + } + } + + if i >= 0 { + resArray[i] = resArray[len(resArray)-1] + resArray[len(resArray)-1] = nil + resArray = resArray[:len(resArray)-1] + } + + if curRes == nil { + return + } + + if !op.IsCurrentReservation(tgtName, devUUID, res) { + return + } + + if (curRes.Type != PR_TYPE_WRITE_EXCLUSIVE_ALLREG && + curRes.Type != PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) || + len(resArray) == 0 { + curRes.Scope = 0 + curRes.Type = 0 + LURes.CurrentReservation = nil + for i, tmpRes = range resArray { + if tmpRes == res { + continue + } + //TODO send sense code + } + LURes.PRGeneration++ + } else { + for i, tmpRes = range resArray { + if tmpRes != res { + //kep scope and type + LURes.CurrentReservation = tmpRes + tmpRes.Scope = curRes.Scope + tmpRes.Type = curRes.Type + break + } + } + LURes.PRGeneration++ + } + +} + +func (op *SCSISimpleReservationOperator) RemoveReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) { + var ( + i int = -1 + tmpRes *api.SCSIReservation + ) + + resArray := op.GetReservationList(tgtName, devUUID) + + for i, tmpRes = range resArray { + if tmpRes == res { + break + } + } + + if i >= 0 { + resArray[i] = resArray[len(resArray)-1] + resArray[len(resArray)-1] = nil + resArray = resArray[:len(resArray)-1] + } +} + +func (op *SCSISimpleReservationOperator) AddReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return false + } + if LURes, ok = targetRes[devUUID]; !ok { + return false + } + LURes.Reservations = append(LURes.Reservations, res) + return true +} + +func (op *SCSISimpleReservationOperator) IsKeyExists(tgtName string, devUUID uint64, key uint64) bool { + + resList := op.GetReservationList(tgtName, devUUID) + for _, tmpRes := range resList { + if tmpRes.Key == key { + return true + } + } + + return false +} + +func (op *SCSISimpleReservationOperator) IsCurrentReservation(tgtName string, devUUID uint64, res *api.SCSIReservation) bool { + curRes := op.GetCurrentReservation(tgtName, devUUID) + if curRes == nil { + return false + } + if curRes.Type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || + curRes.Type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG { + return true + } + + if curRes == res { + return true + } + + return false +} + +func (op *SCSISimpleReservationOperator) RemoveAllReservation(tgtName string, devUUID uint64) { + var ( + LURes *SCSILUReservation + targetRes SCSILUReservationMap + ok bool + ) + if targetRes, ok = op.targetReservations[tgtName]; !ok { + return + } + if LURes, ok = targetRes[devUUID]; !ok { + return + } + LURes.Reservations = []*api.SCSIReservation{} + +} + +func (op *SCSISimpleReservationOperator) Save(tgtName string, devUUID uint64) bool { + return true +} diff --git a/pkg/scsi/scsilumap.go b/pkg/scsi/scsilumap.go index 534528b..08cea95 100644 --- a/pkg/scsi/scsilumap.go +++ b/pkg/scsi/scsilumap.go @@ -65,6 +65,8 @@ func GetTargetLUNMap(tgtName string) api.LUNMap { } func InitSCSILUMap(config *config.Config) error { + var simpleOp *SCSISimpleReservationOperator + var ok bool globalSCSILUMap.mutex.Lock() defer globalSCSILUMap.mutex.Unlock() @@ -83,6 +85,11 @@ func InitSCSILUMap(config *config.Config) error { return errors.New("LU Number must be a number") } mappingLUN(deviceID, lun, tgtName) + //Init SCSISimpleReservationOperator + op := GetSCSIReservationOperator() + if simpleOp, ok = op.(*SCSISimpleReservationOperator); ok { + simpleOp.InitLUReservation(tgtName, deviceID) + } } } return nil diff --git a/pkg/scsi/spc.go b/pkg/scsi/spc.go index 2820b6c..db6ff9c 100644 --- a/pkg/scsi/spc.go +++ b/pkg/scsi/spc.go @@ -25,6 +25,7 @@ import ( "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" "github.com/gostor/gotgt/pkg/util" + "github.com/satori/go.uuid" ) func SPCIllegalOp(host int, cmd *api.SCSICommand) api.SAMStat { @@ -568,9 +569,7 @@ func reportOpcodeOne(cmd *api.SCSICommand, rctd int, opcode byte, rsa uint16, se return nil } -// This is useful for the various commands using the SERVICE ACTION format. -func SPCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat { - // TODO +func SPCReportSupportedOperationCodes(host int, cmd *api.SCSICommand) api.SAMStat { scb := cmd.SCB.Bytes() reporting_options := scb[2] & 0x07 opcode := scb[3] @@ -609,15 +608,60 @@ sense: return api.SAMStatCheckCondition } +// This is useful for the various commands using the SERVICE ACTION format. +func SPCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat { + + scb := cmd.SCB.Bytes() + opcode := int(scb[0]) + action := uint8(scb[1] & 0x1F) + serviceAction := cmd.Device.DeviceProtocol.PerformServiceAction(opcode, action) + if serviceAction == nil { + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition + } else { + fnop := serviceAction.(*SCSIServiceAction) + return fnop.CommandPerformFunc(host, cmd) + } +} + func SPCPRReadKeys(host int, cmd *api.SCSICommand) api.SAMStat { - allocationLength := util.GetUnalignedUint32(cmd.SCB.Bytes()[7:9]) + var ( + buf = &bytes.Buffer{} + data []byte = []byte{} + addBuf = &bytes.Buffer{} + allocationLength uint16 + additionLength uint32 + ) + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + PRGeneration, _ := scsiResOp.GetPRGeneration(tgtName, devUUID) + resList := scsiResOp.GetReservationList(tgtName, devUUID) + length, _ := SCSICDBBufXLength(cmd.SCB.Bytes()) + + allocationLength = uint16(length) if allocationLength < 8 { goto sense } - if cmd.InSDBBuffer.Length < allocationLength { - goto sense + + for _, res := range resList { + addBuf.Write(util.MarshalUint64(res.Key)) } - // TODO + additionLength = uint32(len(addBuf.Bytes())) + + buf.Write(util.MarshalUint32(PRGeneration)) + buf.Write(util.MarshalUint32(additionLength)) + buf.Write(addBuf.Bytes()) + data = buf.Bytes() + if allocationLength < uint16(additionLength) { + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data[0:allocationLength]) + } else { + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data) + } + + cmd.InSDBBuffer.Resid = int32(additionLength) + return api.SAMStatGood sense: cmd.InSDBBuffer.Resid = 0 BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) @@ -625,7 +669,59 @@ sense: } func SPCPRReadReservation(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf = &bytes.Buffer{} + data []byte = []byte{} + addBuf = &bytes.Buffer{} + allocationLength uint16 + additionLength uint32 + ) + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + PRGeneration, _ := scsiResOp.GetPRGeneration(tgtName, devUUID) + curRes := scsiResOp.GetCurrentReservation(tgtName, devUUID) + + length, _ := SCSICDBBufXLength(cmd.SCB.Bytes()) + allocationLength = uint16(length) + if allocationLength < 8 { + goto sense + } + + if curRes == nil { + additionLength = 0 + } else { + addBuf.Write(util.MarshalUint64(curRes.Key)) + //Obsolete + addBuf.WriteByte(0x00) + addBuf.WriteByte(0x00) + addBuf.WriteByte(0x00) + addBuf.WriteByte(0x00) + //Reserved + addBuf.WriteByte(0x00) + //SCOPE and TYPE + scope_type := (curRes.Scope << 4) | curRes.Type + addBuf.WriteByte(scope_type) + additionLength = uint32(0x10) + } + + buf.Write(util.MarshalUint32(PRGeneration)) + buf.Write(util.MarshalUint32(additionLength)) + buf.Write(addBuf.Bytes()) + data = buf.Bytes() + if allocationLength < uint16(additionLength) { + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data[0:allocationLength]) + } else { + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data) + } + + cmd.InSDBBuffer.Resid = int32(additionLength) return api.SAMStatGood + +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRReportCapabilities(host int, cmd *api.SCSICommand) api.SAMStat { @@ -672,28 +768,466 @@ sense: return api.SAMStatCheckCondition } +func reservationCheck(host int, cmd *api.SCSICommand) bool { + var ( + paramLen uint32 + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + ) + length, _ := SCSICDBBufXLength(cmd.SCB.Bytes()) + paramLen = uint32(length) + if paramLen != 24 { + return false + } + spec_i_pt := uint8(buf[20] & 0x08) + all_tg_pt := uint8(buf[20] & 0x04) + aptpl := uint8(buf[20] & 0x01) + /* Currently, We don't support these flags */ + if (spec_i_pt | all_tg_pt | aptpl) > 0 { + return false + } + return true +} + func SPCPRRegister(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + scb []byte = cmd.SCB.Bytes() + ignoreKey bool = false + ok bool = false + resKey uint64 + sAResKey uint64 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + res := scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + + if scb[1] == PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY { + ignoreKey = true + } + ok = reservationCheck(host, cmd) + if !ok { + goto sense + } + resKey = util.GetUnalignedUint64(buf[0:8]) + sAResKey = util.GetUnalignedUint64(buf[8:16]) + + if res != nil { + if ignoreKey || resKey == res.Key { + if sAResKey != 0 { + res.Key = sAResKey + } else { + scsiResOp.DeleteAndRemoveReservation(tgtName, devUUID, res) + } + } else { + return api.SAMStatReservationConflict + } + } else { + if ignoreKey || resKey == 0 { + if sAResKey != 0 { + newRes := &api.SCSIReservation{ + ID: uuid.NewV1(), + Key: sAResKey, + ITNexusID: cmd.ITNexusID, + } + scsiResOp.AddReservation(tgtName, devUUID, newRes) + } + } else { + return api.SAMStatReservationConflict + } + } + scsiResOp.IncPRGeneration(tgtName, devUUID) + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood + +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRReserve(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + scb []byte = cmd.SCB.Bytes() + curRes *api.SCSIReservation + res *api.SCSIReservation + ok bool = false + resScope uint8 + resType uint8 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + + ok = reservationCheck(host, cmd) + if !ok { + goto sense + } + + resScope = scb[2] & 0xf0 >> 4 + resType = scb[2] & 0x0f + + switch resType { + case PR_TYPE_WRITE_EXCLUSIVE_REGONLY, + PR_TYPE_EXCLUSIVE_ACCESS_REGONLY, + PR_TYPE_WRITE_EXCLUSIVE_ALLREG, + PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: + break + default: + goto sense + } + if resScope != PR_LU_SCOPE { + goto sense + } + + res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + if res == nil { + return api.SAMStatReservationConflict + } + + curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID) + if curRes != nil { + if !scsiResOp.IsCurrentReservation(tgtName, devUUID, res) { + return api.SAMStatReservationConflict + } + + if curRes.Type != resType || + curRes.Scope != resScope { + return api.SAMStatReservationConflict + } + } + res.Scope = resScope + res.Type = resType + scsiResOp.SetCurrentReservation(tgtName, devUUID, res) + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRRelease(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + scb []byte = cmd.SCB.Bytes() + curRes *api.SCSIReservation + res *api.SCSIReservation + resList []*api.SCSIReservation + ok bool = false + resKey uint64 + resScope uint8 + resType uint8 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + + ok = reservationCheck(host, cmd) + if !ok { + goto sense + } + + resScope = scb[2] & 0xf0 >> 4 + resType = scb[2] & 0x0f + resKey = util.GetUnalignedUint64(buf[0:8]) + + res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + if res == nil { + return api.SAMStatReservationConflict + } + + curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID) + if curRes == nil { + return api.SAMStatGood + } + + if !scsiResOp.IsCurrentReservation(tgtName, devUUID, res) { + return api.SAMStatGood + } + + if resKey != res.Key { + return api.SAMStatReservationConflict + } + + if curRes.Scope != resScope || curRes.Type != resType { + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION) + return api.SAMStatCheckCondition + } + + scsiResOp.SetCurrentReservation(tgtName, devUUID, nil) + res.Scope = 0 + res.Type = 0 + + switch resType { + case PR_TYPE_WRITE_EXCLUSIVE, + PR_TYPE_EXCLUSIVE_ACCESS, + PR_TYPE_WRITE_EXCLUSIVE_REGONLY, + PR_TYPE_EXCLUSIVE_ACCESS_REGONLY, + PR_TYPE_WRITE_EXCLUSIVE_ALLREG, + PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: + break + default: + goto sense + } + + resList = scsiResOp.GetReservationList(tgtName, devUUID) + for _, tmpRes := range resList { + if tmpRes.ID == res.ID { + continue + } + //TODO send sense code + } + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRClear(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + curRes *api.SCSIReservation + res *api.SCSIReservation + ok bool = false + resKey uint64 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + resList := scsiResOp.GetReservationList(tgtName, devUUID) + + ok = reservationCheck(host, cmd) + if !ok { + goto sense + } + + resKey = util.GetUnalignedUint64(buf[0:8]) + + res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + + if res == nil { + return api.SAMStatReservationConflict + } + + if res.Key != resKey { + return api.SAMStatReservationConflict + } + + curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID) + + if curRes != nil { + curRes.Scope = 0 + curRes.Type = 0 + scsiResOp.SetCurrentReservation(tgtName, devUUID, nil) + } + + for _, tmpRes := range resList { + if tmpRes != res { + //TODO send sense code + } + scsiResOp.DeleteAndRemoveReservation(tgtName, devUUID, tmpRes) + } + scsiResOp.IncPRGeneration(tgtName, devUUID) + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRPreempt(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + scb []byte = cmd.SCB.Bytes() + ok bool = false + resKey uint64 + sAResKey uint64 + res *api.SCSIReservation + curRes *api.SCSIReservation + resReleased bool + removeAllRes bool + resScope uint8 + resType uint8 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + resList := scsiResOp.GetReservationList(tgtName, devUUID) + + ok = reservationCheck(host, cmd) + if !ok { + goto sense + } + resScope = scb[2] & 0xf0 >> 4 + resType = scb[2] & 0x0f + resKey = util.GetUnalignedUint64(buf[0:8]) + sAResKey = util.GetUnalignedUint64(buf[8:16]) + + res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + + if res == nil { + return api.SAMStatReservationConflict + } + + if res.Key != resKey { + return api.SAMStatReservationConflict + } + + if sAResKey != 0 { + ok = scsiResOp.IsKeyExists(tgtName, devUUID, sAResKey) + if ok { + return api.SAMStatReservationConflict + } + } + + curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID) + if curRes != nil { + if curRes.Type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || + curRes.Type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG { + if sAResKey == 0 { + if resType != curRes.Type || + resScope != curRes.Scope { + resReleased = true + } + res.Type = resType + res.Scope = resScope + scsiResOp.SetCurrentReservation(tgtName, devUUID, res) + removeAllRes = true + } + } else { + if curRes.Key == resKey { + if resType != curRes.Type || + resScope != curRes.Scope { + resReleased = true + } + res.Type = resType + res.Scope = resScope + scsiResOp.SetCurrentReservation(tgtName, devUUID, res) + } else { + if sAResKey == 0 { + goto sense + } + } + } + } + + for _, tmpRes := range resList { + if tmpRes == res { + continue + } + + if res.Key == resKey || removeAllRes { + //TODO send sense code + scsiResOp.RemoveReservation(tgtName, devUUID, res) + } else { + if resReleased { + //TODO send sense code + } + } + } + scsiResOp.IncPRGeneration(tgtName, devUUID) + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRRegisterAndMove(host int, cmd *api.SCSICommand) api.SAMStat { + var ( + buf []byte = cmd.OutSDBBuffer.Buffer.Bytes() + scb []byte = cmd.SCB.Bytes() + resKey uint64 + sAResKey uint64 + curRes, dstReg, res *api.SCSIReservation + paramListLen uint32 + unreg uint8 + aptpl uint8 + tpidDataLen uint32 + //idLen uint32 + ) + + tgtName := cmd.Target.Name + devUUID := cmd.Device.UUID + scsiResOp := GetSCSIReservationOperator() + resList := scsiResOp.GetReservationList(tgtName, devUUID) + + paramListLen = util.GetUnalignedUint32(scb[5:9]) + if paramListLen < 24 { + goto sense + } + aptpl = buf[17] & 0x01 + if aptpl != 0 { /* no reported in capabilities */ + goto sense + } + + unreg = buf[17] & 0x02 + + resKey = util.GetUnalignedUint64(buf[0:8]) + sAResKey = util.GetUnalignedUint64(buf[8:16]) + + tpidDataLen = util.GetUnalignedUint32(buf[20:25]) + if tpidDataLen < 24 || (tpidDataLen%4) != 0 { + goto sense + } + + if (paramListLen - 24) < tpidDataLen { + goto sense + } + + res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID) + curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID) + if res == nil { + if curRes != nil { + return api.SAMStatReservationConflict + } else { + goto sense + } + } + + if scsiResOp.IsCurrentReservation(tgtName, devUUID, res) { + return api.SAMStatGood + } + + if res.Key != resKey { + return api.SAMStatReservationConflict + } + + if sAResKey == 0 { + return api.SAMStatReservationConflict + } + + for _, dstReg = range resList { + if dstReg.Key == sAResKey { + goto found + } + } + + goto sense +found: + //TODO check transportid + scsiResOp.SetCurrentReservation(tgtName, devUUID, dstReg) + if unreg != 0 { + scsiResOp.RemoveReservation(tgtName, devUUID, res) + } + scsiResOp.IncPRGeneration(tgtName, devUUID) + scsiResOp.Save(tgtName, devUUID) return api.SAMStatGood +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } /* diff --git a/pkg/scsi/target.go b/pkg/scsi/target.go index 8128dbf..a1a7f30 100644 --- a/pkg/scsi/target.go +++ b/pkg/scsi/target.go @@ -22,6 +22,7 @@ import ( "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" + "github.com/satori/go.uuid" ) func (s *SCSITargetService) NewSCSITarget(tid int, driverName, name string) (*api.SCSITarget, error) { @@ -34,6 +35,7 @@ func (s *SCSITargetService) NewSCSITarget(tid int, driverName, name string) (*ap Name: name, TID: tid, TargetPortGroups: []*api.TargetPortGroup{}, + ITNexus: make(map[uuid.UUID]*api.ITNexus), } tpg := &api.TargetPortGroup{0, []*api.SCSITargetPort{}} s.Targets = append(s.Targets, target) @@ -65,6 +67,25 @@ func FindTargetPort(target *api.SCSITarget, relPortID uint16) *api.SCSITargetPor return nil } +func AddITNexus(target *api.SCSITarget, itnexus *api.ITNexus) bool { + var ret bool = true + target.ITNexusMutex.Lock() + defer target.ITNexusMutex.Unlock() + if _, ok := target.ITNexus[itnexus.ID]; !ok { + target.ITNexus[itnexus.ID] = itnexus + ret = true + } else { + ret = false + } + return ret +} + +func RemoveITNexus(target *api.SCSITarget, itnexus *api.ITNexus) { + target.ITNexusMutex.Lock() + defer target.ITNexusMutex.Unlock() + delete(target.ITNexus, itnexus.ID) +} + func deviceReserve(cmd *api.SCSICommand) error { var lu *api.SCSILu lun := *(*uint64)(unsafe.Pointer(&cmd.Lun)) @@ -80,15 +101,15 @@ func deviceReserve(cmd *api.SCSICommand) error { return nil } - if lu.ReserveID != 0 && lu.ReserveID != cmd.CommandITNID { - glog.Errorf("already reserved %d, %d", lu.ReserveID, cmd.CommandITNID) + if !uuid.Equal(lu.ReserveID, uuid.Nil) && uuid.Equal(lu.ReserveID, cmd.ITNexusID) { + glog.Errorf("already reserved %d, %d", lu.ReserveID, cmd.ITNexusID) return fmt.Errorf("already reserved") } - lu.ReserveID = cmd.CommandITNID + lu.ReserveID = cmd.ITNexusID return nil } -func deviceRelease(tid int, itn, lun uint64, force bool) error { +func deviceRelease(tid int, itn uuid.UUID, lun uint64, force bool) error { // TODO return nil }