mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
parent
80a351633f
commit
7923b83953
@ -280,7 +280,7 @@ func getURLScheme(tls bool) string {
|
||||
}
|
||||
|
||||
// getObjectLocation gets the fully qualified URL of an object.
|
||||
func getObjectLocation(r *http.Request, domain, bucket, object string) string {
|
||||
func getObjectLocation(r *http.Request, domains []string, bucket, object string) string {
|
||||
// unit tests do not have host set.
|
||||
if r.Host == "" {
|
||||
return path.Clean(r.URL.Path)
|
||||
@ -295,10 +295,11 @@ func getObjectLocation(r *http.Request, domain, bucket, object string) string {
|
||||
Scheme: proto,
|
||||
}
|
||||
// If domain is set then we need to use bucket DNS style.
|
||||
if domain != "" {
|
||||
for _, domain := range domains {
|
||||
if strings.Contains(r.Host, domain) {
|
||||
u.Host = bucket + "." + r.Host
|
||||
u.Path = path.Join(slashSeparator, object)
|
||||
break
|
||||
}
|
||||
}
|
||||
return u.String()
|
||||
|
@ -26,7 +26,7 @@ func TestObjectLocation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
request *http.Request
|
||||
bucket, object string
|
||||
domain string
|
||||
domains []string
|
||||
expectedLocation string
|
||||
}{
|
||||
// Server binding to localhost IP with https.
|
||||
@ -80,7 +80,7 @@ func TestObjectLocation(t *testing.T) {
|
||||
Host: "mys3.bucket.org",
|
||||
Header: map[string][]string{},
|
||||
},
|
||||
domain: "mys3.bucket.org",
|
||||
domains: []string{"mys3.bucket.org"},
|
||||
bucket: "mybucket",
|
||||
object: "test/1.txt",
|
||||
expectedLocation: "http://mybucket.mys3.bucket.org/test/1.txt",
|
||||
@ -92,14 +92,14 @@ func TestObjectLocation(t *testing.T) {
|
||||
"X-Forwarded-Scheme": {httpsScheme},
|
||||
},
|
||||
},
|
||||
domain: "mys3.bucket.org",
|
||||
domains: []string{"mys3.bucket.org"},
|
||||
bucket: "mybucket",
|
||||
object: "test/1.txt",
|
||||
expectedLocation: "https://mybucket.mys3.bucket.org/test/1.txt",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
gotLocation := getObjectLocation(testCase.request, testCase.domain, testCase.bucket, testCase.object)
|
||||
gotLocation := getObjectLocation(testCase.request, testCase.domains, testCase.bucket, testCase.object)
|
||||
if testCase.expectedLocation != gotLocation {
|
||||
t.Errorf("Test %d: expected %s, got %s", i+1, testCase.expectedLocation, gotLocation)
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
|
||||
// API Router
|
||||
apiRouter := router.PathPrefix("/").Subrouter()
|
||||
var routers []*mux.Router
|
||||
if globalDomainName != "" {
|
||||
routers = append(routers, apiRouter.Host("{bucket:.+}."+globalDomainName).Subrouter())
|
||||
for _, domainName := range globalDomainNames {
|
||||
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName).Subrouter())
|
||||
}
|
||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||
|
||||
|
@ -446,7 +446,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// Make sure to add Location information here only for bucket
|
||||
w.Header().Set("Location", getObjectLocation(r, globalDomainName, bucket, ""))
|
||||
w.Header().Set("Location", getObjectLocation(r, globalDomainNames, bucket, ""))
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
return
|
||||
@ -505,7 +505,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
resource, err := getResource(r.URL.Path, r.Host, globalDomainName)
|
||||
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@ -677,7 +677,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
location := getObjectLocation(r, globalDomainName, bucket, object)
|
||||
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
||||
w.Header().Set("ETag", `"`+objInfo.ETag+`"`)
|
||||
w.Header().Set("Location", location)
|
||||
|
||||
|
@ -262,10 +262,14 @@ func handleCommonEnvVars() {
|
||||
logger.FatalIf(err, "Unable to initialize etcd with %s", etcdEndpoints)
|
||||
}
|
||||
|
||||
globalDomainName, globalIsEnvDomainName = os.LookupEnv("MINIO_DOMAIN")
|
||||
if globalDomainName != "" {
|
||||
if _, ok = dns2.IsDomainName(globalDomainName); !ok {
|
||||
logger.Fatal(uiErrInvalidDomainValue(nil).Msg("Unknown value `%s`", globalDomainName), "Invalid MINIO_DOMAIN value in environment variable")
|
||||
v, ok := os.LookupEnv("MINIO_DOMAIN")
|
||||
if ok {
|
||||
for _, domainName := range strings.Split(v, ",") {
|
||||
if _, ok = dns2.IsDomainName(domainName); !ok {
|
||||
logger.Fatal(uiErrInvalidDomainValue(nil).Msg("Unknown value `%s`", domainName),
|
||||
"Invalid MINIO_DOMAIN value in environment variable")
|
||||
}
|
||||
globalDomainNames = append(globalDomainNames, domainName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,10 +298,10 @@ func handleCommonEnvVars() {
|
||||
updateDomainIPs(localIP4)
|
||||
}
|
||||
|
||||
if globalDomainName != "" && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
||||
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
||||
var err error
|
||||
globalDNSConfig, err = dns.NewCoreDNS(globalDomainName, globalDomainIPs, globalMinioPort, globalEtcdClient)
|
||||
logger.FatalIf(err, "Unable to initialize DNS config for %s.", globalDomainName)
|
||||
globalDNSConfig, err = dns.NewCoreDNS(globalDomainNames, globalDomainIPs, globalMinioPort, globalEtcdClient)
|
||||
logger.FatalIf(err, "Unable to initialize DNS config for %s.", globalDomainNames)
|
||||
}
|
||||
|
||||
if drives := os.Getenv("MINIO_CACHE_DRIVES"); drives != "" {
|
||||
|
@ -130,8 +130,8 @@ func TestServerConfigWithEnvs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check if serverConfig has the correct domain
|
||||
if globalDomainName != "domain.com" {
|
||||
t.Errorf("Expected Domain to be `domain.com`, found " + globalDomainName)
|
||||
if globalDomainNames[0] != "domain.com" {
|
||||
t.Errorf("Expected Domain to be `domain.com`, found " + globalDomainNames[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,7 +623,7 @@ type bucketForwardingHandler struct {
|
||||
}
|
||||
|
||||
func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if globalDNSConfig == nil || globalDomainName == "" ||
|
||||
if globalDNSConfig == nil || len(globalDomainNames) == 0 ||
|
||||
guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
|
||||
guessIsRPCReq(r) || isAdminReq(r) {
|
||||
f.handler.ServeHTTP(w, r)
|
||||
@ -632,7 +632,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// For browser requests, when federation is setup we need to
|
||||
// specifically handle download and upload for browser requests.
|
||||
if guessIsBrowserReq(r) && globalDNSConfig != nil && globalDomainName != "" {
|
||||
if guessIsBrowserReq(r) && globalDNSConfig != nil && len(globalDomainNames) > 0 {
|
||||
var bucket, _ string
|
||||
switch r.Method {
|
||||
case http.MethodPut:
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/certs"
|
||||
"github.com/minio/minio/pkg/dns"
|
||||
"github.com/minio/minio/pkg/iam/policy"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/iam/validator"
|
||||
)
|
||||
|
||||
@ -175,9 +175,8 @@ var (
|
||||
globalActiveCred auth.Credentials
|
||||
globalPublicCerts []*x509.Certificate
|
||||
|
||||
globalIsEnvDomainName bool
|
||||
globalDomainName string // Root domain for virtual host style requests
|
||||
globalDomainIPs set.StringSet // Root domain IP address(s) for a distributed Minio deployment
|
||||
globalDomainNames []string // Root domains for virtual host style requests
|
||||
globalDomainIPs set.StringSet // Root domain IP address(s) for a distributed Minio deployment
|
||||
|
||||
globalListingTimeout = newDynamicTimeout( /*30*/ 600*time.Second /*5*/, 600*time.Second) // timeout for listing related ops
|
||||
globalObjectTimeout = newDynamicTimeout( /*1*/ 10*time.Minute /*10*/, 600*time.Second) // timeout for Object API related ops
|
||||
|
@ -341,8 +341,8 @@ func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
|
||||
func getResource(path string, host string, domain string) (string, error) {
|
||||
if domain == "" {
|
||||
func getResource(path string, host string, domains []string) (string, error) {
|
||||
if len(domains) == 0 {
|
||||
return path, nil
|
||||
}
|
||||
// If virtual-host-style is enabled construct the "resource" properly.
|
||||
@ -357,11 +357,14 @@ func getResource(path string, host string, domain string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if !strings.HasSuffix(host, "."+domain) {
|
||||
return path, nil
|
||||
for _, domain := range domains {
|
||||
if !strings.HasSuffix(host, "."+domain) {
|
||||
continue
|
||||
}
|
||||
bucket := strings.TrimSuffix(host, "."+domain)
|
||||
return slashSeparator + pathJoin(bucket, path), nil
|
||||
}
|
||||
bucket := strings.TrimSuffix(host, "."+domain)
|
||||
return slashSeparator + pathJoin(bucket, path), nil
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// If none of the http routes match respond with MethodNotAllowed, in JSON
|
||||
|
@ -213,15 +213,15 @@ func TestGetResource(t *testing.T) {
|
||||
testCases := []struct {
|
||||
p string
|
||||
host string
|
||||
domain string
|
||||
domains []string
|
||||
expectedResource string
|
||||
}{
|
||||
{"/a/b/c", "test.mydomain.com", "mydomain.com", "/test/a/b/c"},
|
||||
{"/a/b/c", "test.mydomain.com", "notmydomain.com", "/a/b/c"},
|
||||
{"/a/b/c", "test.mydomain.com", "", "/a/b/c"},
|
||||
{"/a/b/c", "test.mydomain.com", []string{"mydomain.com"}, "/test/a/b/c"},
|
||||
{"/a/b/c", "test.mydomain.com", []string{"notmydomain.com"}, "/a/b/c"},
|
||||
{"/a/b/c", "test.mydomain.com", nil, "/a/b/c"},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
gotResource, err := getResource(test.p, test.host, test.domain)
|
||||
gotResource, err := getResource(test.p, test.host, test.domains)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -2370,7 +2370,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
}
|
||||
|
||||
// Get object location.
|
||||
location := getObjectLocation(r, globalDomainName, bucket, object)
|
||||
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
||||
// Generate complete multipart response.
|
||||
response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo.ETag)
|
||||
var encodedSuccessResponse []byte
|
||||
|
@ -163,7 +163,7 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
return ErrExpiredPresignRequest
|
||||
}
|
||||
|
||||
encodedResource, err = getResource(encodedResource, r.Host, globalDomainName)
|
||||
encodedResource, err = getResource(encodedResource, r.Host, globalDomainNames)
|
||||
if err != nil {
|
||||
return ErrInvalidRequest
|
||||
}
|
||||
@ -257,7 +257,7 @@ func doesSignV2Match(r *http.Request) APIErrorCode {
|
||||
return ErrInvalidQueryParams
|
||||
}
|
||||
|
||||
encodedResource, err = getResource(encodedResource, r.Host, globalDomainName)
|
||||
encodedResource, err = getResource(encodedResource, r.Host, globalDomainNames)
|
||||
if err != nil {
|
||||
return ErrInvalidRequest
|
||||
}
|
||||
|
@ -156,6 +156,12 @@ export MINIO_DOMAIN=mydomain.com
|
||||
minio server /data
|
||||
```
|
||||
|
||||
For advanced use cases `MINIO_DOMAIN` environment variable supports multiple-domains with comma separated values.
|
||||
```sh
|
||||
export MINIO_DOMAIN=sub1.mydomain.com,sub2.mydomain.com
|
||||
minio server /data
|
||||
```
|
||||
|
||||
### Drive Sync
|
||||
|
||||
By default, Minio writes to disk in synchronous mode for all metadata operations. Set `MINIO_DRIVE_SYNC` environment variable to enable synchronous mode for all data operations as well.
|
||||
|
@ -47,14 +47,30 @@ func newCoreDNSMsg(bucket, ip string, port int, ttl uint32) ([]byte, error) {
|
||||
|
||||
// Retrieves list of DNS entries for the domain.
|
||||
func (c *coreDNS) List() ([]SrvRecord, error) {
|
||||
key := msg.Path(fmt.Sprintf("%s.", c.domainName), defaultPrefixPath)
|
||||
return c.list(key)
|
||||
var srvRecords []SrvRecord
|
||||
for _, domainName := range c.domainNames {
|
||||
key := msg.Path(fmt.Sprintf("%s.", domainName), defaultPrefixPath)
|
||||
records, err := c.list(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srvRecords = append(srvRecords, records...)
|
||||
}
|
||||
return srvRecords, nil
|
||||
}
|
||||
|
||||
// Retrieves DNS records for a bucket.
|
||||
func (c *coreDNS) Get(bucket string) ([]SrvRecord, error) {
|
||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath)
|
||||
return c.list(key)
|
||||
var srvRecords []SrvRecord
|
||||
for _, domainName := range c.domainNames {
|
||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), defaultPrefixPath)
|
||||
records, err := c.list(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srvRecords = append(srvRecords, records...)
|
||||
}
|
||||
return srvRecords, nil
|
||||
}
|
||||
|
||||
// Retrieves list of entries under the key passed.
|
||||
@ -92,12 +108,14 @@ func (c *coreDNS) list(key string) ([]SrvRecord, error) {
|
||||
//
|
||||
// In all other situations when we want to list all DNS records,
|
||||
// which is handled in the else clause.
|
||||
if key != msg.Path(fmt.Sprintf(".%s.", c.domainName), defaultPrefixPath) {
|
||||
if srvRecord.Key == "/" {
|
||||
for _, domainName := range c.domainNames {
|
||||
if key != msg.Path(fmt.Sprintf(".%s.", domainName), defaultPrefixPath) {
|
||||
if srvRecord.Key == "/" {
|
||||
srvRecords = append(srvRecords, srvRecord)
|
||||
}
|
||||
} else {
|
||||
srvRecords = append(srvRecords, srvRecord)
|
||||
}
|
||||
} else {
|
||||
srvRecords = append(srvRecords, srvRecord)
|
||||
}
|
||||
|
||||
}
|
||||
@ -117,16 +135,18 @@ func (c *coreDNS) Put(bucket string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := msg.Path(fmt.Sprintf("%s.%s", bucket, c.domainName), defaultPrefixPath)
|
||||
key = key + "/" + ip
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
_, err = c.etcdClient.Put(ctx, key, string(bucketMsg))
|
||||
defer cancel()
|
||||
if err != nil {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
c.etcdClient.Delete(ctx, key)
|
||||
for _, domainName := range c.domainNames {
|
||||
key := msg.Path(fmt.Sprintf("%s.%s", bucket, domainName), defaultPrefixPath)
|
||||
key = key + "/" + ip
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
_, err = c.etcdClient.Put(ctx, key, string(bucketMsg))
|
||||
defer cancel()
|
||||
return err
|
||||
if err != nil {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
c.etcdClient.Delete(ctx, key)
|
||||
defer cancel()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -134,33 +154,35 @@ func (c *coreDNS) Put(bucket string) error {
|
||||
|
||||
// Removes DNS entries added in Put().
|
||||
func (c *coreDNS) Delete(bucket string) error {
|
||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath)
|
||||
srvRecords, err := c.list(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, record := range srvRecords {
|
||||
dctx, dcancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
if _, err = c.etcdClient.Delete(dctx, key+"/"+record.Host); err != nil {
|
||||
dcancel()
|
||||
for _, domainName := range c.domainNames {
|
||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), defaultPrefixPath)
|
||||
srvRecords, err := c.list(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dcancel()
|
||||
for _, record := range srvRecords {
|
||||
dctx, dcancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
if _, err = c.etcdClient.Delete(dctx, key+"/"+record.Host); err != nil {
|
||||
dcancel()
|
||||
return err
|
||||
}
|
||||
dcancel()
|
||||
}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// CoreDNS - represents dns config for coredns server.
|
||||
type coreDNS struct {
|
||||
domainName string
|
||||
domainIPs set.StringSet
|
||||
domainPort int
|
||||
etcdClient *etcd.Client
|
||||
domainNames []string
|
||||
domainIPs set.StringSet
|
||||
domainPort int
|
||||
etcdClient *etcd.Client
|
||||
}
|
||||
|
||||
// NewCoreDNS - initialize a new coreDNS set/unset values.
|
||||
func NewCoreDNS(domainName string, domainIPs set.StringSet, domainPort string, etcdClient *etcd.Client) (Config, error) {
|
||||
if domainName == "" || domainIPs.IsEmpty() {
|
||||
func NewCoreDNS(domainNames []string, domainIPs set.StringSet, domainPort string, etcdClient *etcd.Client) (Config, error) {
|
||||
if len(domainNames) == 0 || domainIPs.IsEmpty() {
|
||||
return nil, errors.New("invalid argument")
|
||||
}
|
||||
|
||||
@ -170,9 +192,9 @@ func NewCoreDNS(domainName string, domainIPs set.StringSet, domainPort string, e
|
||||
}
|
||||
|
||||
return &coreDNS{
|
||||
domainName: domainName,
|
||||
domainIPs: domainIPs,
|
||||
domainPort: port,
|
||||
etcdClient: etcdClient,
|
||||
domainNames: domainNames,
|
||||
domainIPs: domainIPs,
|
||||
domainPort: port,
|
||||
etcdClient: etcdClient,
|
||||
}, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user