Update swift lib for adding the support of Openstack Identity v3 API

Signed-off-by: Li Wenquan <wenquan.li@hp.com>
pull/493/head
davidli 2015-06-08 13:21:46 +08:00 committed by Sylvain Baubeau
parent 3fb42a1502
commit 3ff9f9b9cc
7 changed files with 434 additions and 39 deletions

2
Godeps/Godeps.json generated
View File

@ -87,7 +87,7 @@
},
{
"ImportPath": "github.com/ncw/swift",
"Rev": "021f1ecdb0940ce5c64ce0e27928d9680f85f291"
"Rev": "22c8fa9fb5ba145b4d4e2cebb027e84b1a7b1296"
},
{
"ImportPath": "github.com/yvasiyarov/go-metrics",

View File

@ -34,6 +34,7 @@ Here is a short example from the docs
UserName: "user",
ApiKey: "key",
AuthUrl: "auth_url",
Domain: "domain", // Name of the domain (v3 auth only)
Tenant: "tenant", // Name of the tenant (v2 auth only)
}
// Authenticate
@ -45,7 +46,7 @@ Here is a short example from the docs
containers, err := c.ContainerNames(nil)
fmt.Println(containers)
// etc...
Additions
---------
@ -69,6 +70,25 @@ And optionally these if using v2 authentication
export SWIFT_TENANT='TenantName'
export SWIFT_TENANT_ID='TenantId'
And optionally these if using v3 authentication
export SWIFT_TENANT='TenantName'
export SWIFT_TENANT_ID='TenantId'
export SWIFT_API_DOMAIN_ID='domain id'
export SWIFT_API_DOMAIN='domain name'
And optionally this if you want to skip server certificate validation
export SWIFT_AUTH_INSECURE=1
And optionally this to configure the connect channel timeout, in seconds
export SWIFT_CONNECTION_CHANNEL_TIMEOUT=60
And optionally this to configure the data channel timeout, in seconds
export SWIFT_DATA_CHANNEL_TIMEOUT=60
Then run the tests with `go test`
License
@ -105,3 +125,4 @@ Contributors
- lsowen <lsowen@s1network.com>
- Sylvain Baubeau <sbaubeau@redhat.com>
- Chris Kastorff <encryptio@gmail.com>
- Dai HaoJun <haojun.dai@hp.com>

View File

@ -29,7 +29,9 @@ type Authenticator interface {
func newAuth(c *Connection) (Authenticator, error) {
AuthVersion := c.AuthVersion
if AuthVersion == 0 {
if strings.Contains(c.AuthUrl, "v2") {
if strings.Contains(c.AuthUrl, "v3") {
AuthVersion = 3
} else if strings.Contains(c.AuthUrl, "v2") {
AuthVersion = 2
} else if strings.Contains(c.AuthUrl, "v1") {
AuthVersion = 1
@ -47,6 +49,8 @@ func newAuth(c *Connection) (Authenticator, error) {
// this is just an optimization.
useApiKey: len(c.ApiKey) >= 32,
}, nil
case 3:
return &v3Auth{}, nil
}
return nil, newErrorf(500, "Auth Version %d not supported", AuthVersion)
}

View File

@ -0,0 +1,207 @@
package swift
import (
"bytes"
"encoding/json"
"net/http"
"strings"
)
const (
v3AuthMethodToken = "token"
v3AuthMethodPassword = "password"
v3InterfacePublic = "public"
v3InterfaceInternal = "internal"
v3InterfaceAdmin = "admin"
v3CatalogTypeObjectStore = "object-store"
)
// V3 Authentication request
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
// http://developer.openstack.org/api-ref-identity-v3.html
type v3AuthRequest struct {
Auth struct {
Identity struct {
Methods []string `json:"methods"`
Password *v3AuthPassword `json:"password,omitempty"`
Token *v3AuthToken `json:"token,omitempty"`
} `json:"identity"`
Scope *v3Scope `json:"scope,omitempty"`
} `json:"auth"`
}
type v3Scope struct {
Project *v3Project `json:"project,omitempty"`
Domain *v3Domain `json:"domain,omitempty"`
}
type v3Domain struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type v3Project struct {
Name string `json:"name,omitempty"`
Id string `json:"id,omitempty"`
Domain *v3Domain `json:"domain,omitempty"`
}
type v3User struct {
Domain *v3Domain `json:"domain,omitempty"`
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Password string `json:"password,omitempty"`
}
type v3AuthToken struct {
Id string `json:"id"`
}
type v3AuthPassword struct {
User v3User `json:"user"`
}
// V3 Authentication response
type v3AuthResponse struct {
Token struct {
Expires_At, Issued_At string
Methods []string
Roles []map[string]string
Project struct {
Domain struct {
Id, Name string
}
Id, Name string
}
Catalog []struct {
Id, Namem, Type string
Endpoints []struct {
Id, Region_Id, Url, Region, Interface string
}
}
User struct {
Id, Name string
Domain struct {
Id, Name string
Links struct {
Self string
}
}
}
Audit_Ids []string
}
}
type v3Auth struct {
Auth *v3AuthResponse
Headers http.Header
}
func (auth *v3Auth) Request(c *Connection) (*http.Request, error) {
var v3i interface{}
v3 := v3AuthRequest{}
if c.UserName == "" {
v3.Auth.Identity.Methods = []string{v3AuthMethodToken}
v3.Auth.Identity.Token = &v3AuthToken{Id: c.ApiKey}
} else {
v3.Auth.Identity.Methods = []string{v3AuthMethodPassword}
v3.Auth.Identity.Password = &v3AuthPassword{
User: v3User{
Name: c.UserName,
Password: c.ApiKey,
},
}
var domain *v3Domain
if c.Domain != "" {
domain = &v3Domain{Name: c.Domain}
} else if c.DomainId != "" {
domain = &v3Domain{Id: c.DomainId}
}
v3.Auth.Identity.Password.User.Domain = domain
}
if c.TenantId != "" || c.Tenant != "" {
v3.Auth.Scope = &v3Scope{Project: &v3Project{}}
if c.TenantId != "" {
v3.Auth.Scope.Project.Id = c.TenantId
} else if c.Tenant != "" {
v3.Auth.Scope.Project.Name = c.Tenant
var defaultDomain v3Domain
if c.Domain != "" {
defaultDomain = v3Domain{Name: "Default"}
} else if c.DomainId != "" {
defaultDomain = v3Domain{Id: "Default"}
}
v3.Auth.Scope.Project.Domain = &defaultDomain
}
}
v3i = v3
body, err := json.Marshal(v3i)
if err != nil {
return nil, err
}
url := c.AuthUrl
if !strings.HasSuffix(url, "/") {
url += "/"
}
url += "tokens"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return req, nil
}
func (auth *v3Auth) Response(resp *http.Response) error {
auth.Auth = &v3AuthResponse{}
auth.Headers = resp.Header
err := readJson(resp, auth.Auth)
return err
}
func (auth *v3Auth) endpointUrl(Type string, Internal bool) string {
for _, catalog := range auth.Auth.Token.Catalog {
if catalog.Type == Type {
for _, endpoint := range catalog.Endpoints {
if Internal {
if endpoint.Interface == v3InterfaceInternal {
return endpoint.Url
}
} else {
if endpoint.Interface == v3InterfacePublic {
return endpoint.Url
}
}
}
}
}
return ""
}
func (auth *v3Auth) StorageUrl(Internal bool) string {
return auth.endpointUrl(v3CatalogTypeObjectStore, Internal)
}
func (auth *v3Auth) Token() string {
return auth.Headers.Get("X-Subject-Token")
}
func (auth *v3Auth) CdnUrl() string {
return ""
}

View File

@ -80,6 +80,8 @@ const (
type Connection struct {
// Parameters - fill these in before calling Authenticate
// They are all optional except UserName, ApiKey and AuthUrl
Domain string // User's domain name
DomainId string // User's domain Id
UserName string // UserName for api
ApiKey string // Key for api access
AuthUrl string // Auth URL

View File

@ -16,22 +16,24 @@ import (
"archive/tar"
"bytes"
"crypto/md5"
"crypto/tls"
"encoding/json"
"encoding/xml"
"fmt"
"github.com/ncw/swift"
"github.com/ncw/swift/swifttest"
"io"
"net/http"
"os"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/ncw/swift"
"github.com/ncw/swift/swifttest"
)
var (
c swift.Connection
c *swift.Connection
srv *swifttest.SwiftServer
m1 = swift.Metadata{"Hello": "1", "potato-Salad": "2"}
m2 = swift.Metadata{"hello": "", "potato-salad": ""}
@ -54,36 +56,106 @@ const (
type someTransport struct{ http.Transport }
func TestTransport(t *testing.T) {
func makeConnection() (*swift.Connection, error) {
var err error
UserName := os.Getenv("SWIFT_API_USER")
ApiKey := os.Getenv("SWIFT_API_KEY")
AuthUrl := os.Getenv("SWIFT_AUTH_URL")
Insecure := os.Getenv("SWIFT_AUTH_INSECURE")
ConnectionChannelTimeout := os.Getenv("SWIFT_CONNECTION_CHANNEL_TIMEOUT")
DataChannelTimeout := os.Getenv("SWIFT_DATA_CHANNEL_TIMEOUT")
if UserName == "" || ApiKey == "" || AuthUrl == "" {
if srv != nil {
srv.Close()
}
srv, err = swifttest.NewSwiftServer("localhost")
if err != nil {
t.Fatal("Failed to create server", err)
return nil, err
}
UserName = "swifttest"
ApiKey = "swifttest"
AuthUrl = srv.AuthURL
}
tr := &someTransport{Transport: http.Transport{MaxIdleConnsPerHost: 2048}}
ct := swift.Connection{
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConnsPerHost: 2048,
}
if Insecure == "1" {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
c := swift.Connection{
UserName: UserName,
ApiKey: ApiKey,
AuthUrl: AuthUrl,
Tenant: os.Getenv("SWIFT_TENANT"),
TenantId: os.Getenv("SWIFT_TENANT_ID"),
Transport: tr,
Transport: transport,
ConnectTimeout: 60 * time.Second,
Timeout: 60 * time.Second,
}
err = ct.Authenticate()
var timeout int64
if ConnectionChannelTimeout != "" {
timeout, err = strconv.ParseInt(ConnectionChannelTimeout, 10, 32)
if err == nil {
c.ConnectTimeout = time.Duration(timeout) * time.Second
}
}
if DataChannelTimeout != "" {
timeout, err = strconv.ParseInt(DataChannelTimeout, 10, 32)
if err == nil {
c.Timeout = time.Duration(timeout) * time.Second
}
}
return &c, nil
}
func isV3Api() bool {
AuthUrl := os.Getenv("SWIFT_AUTH_URL")
return strings.Contains(AuthUrl, "v3")
}
func TestTransport(t *testing.T) {
var err error
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
tr := &someTransport{
Transport: http.Transport{
MaxIdleConnsPerHost: 2048,
},
}
Insecure := os.Getenv("SWIFT_AUTH_INSECURE")
if Insecure == "1" {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
c.Transport = tr
if isV3Api() {
c.Tenant = os.Getenv("SWIFT_TENANT")
c.Domain = os.Getenv("SWIFT_API_DOMAIN")
} else {
c.Tenant = os.Getenv("SWIFT_TENANT")
c.TenantId = os.Getenv("SWIFT_TENANT_ID")
}
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !ct.Authenticated() {
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
if srv != nil {
@ -92,27 +164,116 @@ func TestTransport(t *testing.T) {
}
// The following Test functions are run in order - this one must come before the others!
func TestAuthenticate(t *testing.T) {
func TestV1V2Authenticate(t *testing.T) {
var err error
UserName := os.Getenv("SWIFT_API_USER")
ApiKey := os.Getenv("SWIFT_API_KEY")
AuthUrl := os.Getenv("SWIFT_AUTH_URL")
if UserName == "" || ApiKey == "" || AuthUrl == "" {
srv, err = swifttest.NewSwiftServer("localhost")
if err != nil {
t.Fatal("Failed to create server", err)
}
UserName = "swifttest"
ApiKey = "swifttest"
AuthUrl = srv.AuthURL
if isV3Api() {
return
}
c = swift.Connection{
UserName: UserName,
ApiKey: ApiKey,
AuthUrl: AuthUrl,
Tenant: os.Getenv("SWIFT_TENANT"),
TenantId: os.Getenv("SWIFT_TENANT_ID"),
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.Tenant = os.Getenv("SWIFT_TENANT")
c.TenantId = os.Getenv("SWIFT_TENANT_ID")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
}
func TestV3AuthenticateWithDomainNameAndTenantId(t *testing.T) {
var err error
if !isV3Api() {
return
}
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.TenantId = os.Getenv("SWIFT_TENANT_ID")
c.Domain = os.Getenv("SWIFT_API_DOMAIN")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
}
func TestV3AuthenticateWithDomainIdAndTenantId(t *testing.T) {
var err error
if !isV3Api() {
return
}
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.TenantId = os.Getenv("SWIFT_TENANT_ID")
c.DomainId = os.Getenv("SWIFT_API_DOMAIN_ID")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
}
func TestV3AuthenticateWithDomainNameAndTenantName(t *testing.T) {
var err error
if !isV3Api() {
return
}
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.Tenant = os.Getenv("SWIFT_TENANT")
c.Domain = os.Getenv("SWIFT_API_DOMAIN")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
}
func TestV3AuthenticateWithDomainIdAndTenantName(t *testing.T) {
var err error
if !isV3Api() {
return
}
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.Tenant = os.Getenv("SWIFT_TENANT")
c.DomainId = os.Getenv("SWIFT_API_DOMAIN_ID")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)

View File

@ -444,7 +444,7 @@ func (objr objectResource) get(a *action) interface{} {
if start >= cursor+length {
continue
}
segments = append(segments, bytes.NewReader(obj.data[max(0, start - cursor):]))
segments = append(segments, bytes.NewReader(obj.data[max(0, start-cursor):]))
cursor += length
}
}
@ -452,7 +452,7 @@ func (objr objectResource) get(a *action) interface{} {
if end == -1 {
end = size
}
reader = io.LimitReader(io.MultiReader(segments...), int64(end - start))
reader = io.LimitReader(io.MultiReader(segments...), int64(end-start))
} else {
if end == -1 {
end = len(obj.data)
@ -461,7 +461,7 @@ func (objr objectResource) get(a *action) interface{} {
reader = bytes.NewReader(obj.data[start:end])
}
h.Set("Content-Length", fmt.Sprint(end - start))
h.Set("Content-Length", fmt.Sprint(end-start))
h.Set("ETag", hex.EncodeToString(etag))
h.Set("Last-Modified", obj.mtime.Format(http.TimeFormat))
@ -519,7 +519,7 @@ func (objr objectResource) put(a *action) interface{} {
var content_type string
if content_type = a.req.Header.Get("Content-Type"); content_type == "" {
content_type := mime.TypeByExtension(obj.name)
content_type = mime.TypeByExtension(obj.name)
if content_type == "" {
content_type = "application/octet-stream"
}
@ -880,6 +880,6 @@ func NewSwiftServer(address string) (*SwiftServer, error) {
return server, nil
}
func (srv SwiftServer) Close() {
func (srv *SwiftServer) Close() {
srv.Listener.Close()
}