forked from rook/rook
We have introduced a new goroutine to check the state of the rgw
endpoint. It will run every minute and perform operations on a bucket.
The success or failure will be reported as part of the status field of
the CephObjectStore CR.
A good status will look like:
status:
endpointStatus:
lastChanged: "2020-06-25T13:47:45Z"
lastChecked: "2020-06-25T13:48:46Z"
phase: Connected
A failed status:
status:
endpointStatus:
details: |-
error creating bucket "rook-ceph-internal-s3-bucket-checker": RequestError: send request failed
caused by: Put http://rook-ceph-rgw-my-store.rook-ceph:8080/rook-ceph-internal-s3-bucket-checker: dial tcp 10.108.189.148:8080: connect: connection refused
health: ERROR
This check works for both converged and external modes. Note that the
CephObjectStore CRD has a new field called "externalRgwEndpoints" which
allows you to define a list of IP addresses pointing to rgws.
Closes: https://github.com/rook/rook/issues/5692
Signed-off-by: Sébastien Han <seb@redhat.com>
230 lines
6.7 KiB
Go
230 lines
6.7 KiB
Go
/*
|
|
Copyright 2016 The Rook Authors. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
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.
|
|
*/
|
|
|
|
package object
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rook/rook/pkg/util/exec"
|
|
)
|
|
|
|
const (
|
|
RGWErrorNone = iota
|
|
RGWErrorUnknown
|
|
RGWErrorNotFound
|
|
RGWErrorBadData
|
|
RGWErrorParse
|
|
ErrorCodeFileExists = 17
|
|
)
|
|
|
|
// An ObjectUser defines the details of an object store user.
|
|
type ObjectUser struct {
|
|
UserID string `json:"userId"`
|
|
DisplayName *string `json:"displayName"`
|
|
Email *string `json:"email"`
|
|
AccessKey *string `json:"accessKey"`
|
|
SecretKey *string `json:"secretKey"`
|
|
}
|
|
|
|
// ListUsers lists the object pool users.
|
|
func ListUsers(c *Context) ([]string, int, error) {
|
|
result, err := runAdminCommand(c, "user", "list")
|
|
if err != nil {
|
|
return nil, RGWErrorUnknown, errors.Wrap(err, "failed to list users")
|
|
}
|
|
|
|
var s []string
|
|
if err := json.Unmarshal([]byte(result), &s); err != nil {
|
|
return nil, RGWErrorParse, errors.Wrapf(err, "failed to read users info result=%s", result)
|
|
}
|
|
|
|
return s, RGWErrorNone, nil
|
|
}
|
|
|
|
type rgwUserInfo struct {
|
|
UserID string `json:"user_id"`
|
|
DisplayName string `json:"display_name"`
|
|
Email string `json:"email"`
|
|
Keys []struct {
|
|
AccessKey string `json:"access_key"`
|
|
SecretKey string `json:"secret_key"`
|
|
}
|
|
}
|
|
|
|
func decodeUser(data string) (*ObjectUser, int, error) {
|
|
var user rgwUserInfo
|
|
err := json.Unmarshal([]byte(data), &user)
|
|
if err != nil {
|
|
return nil, RGWErrorParse, errors.Wrapf(err, "failed to unmarshal json. %s", data)
|
|
}
|
|
|
|
rookUser := ObjectUser{UserID: user.UserID, DisplayName: &user.DisplayName, Email: &user.Email}
|
|
|
|
if len(user.Keys) > 0 {
|
|
rookUser.AccessKey = &user.Keys[0].AccessKey
|
|
rookUser.SecretKey = &user.Keys[0].SecretKey
|
|
}
|
|
|
|
return &rookUser, RGWErrorNone, nil
|
|
}
|
|
|
|
// GetUser returns the user with the given ID.
|
|
func GetUser(c *Context, id string) (*ObjectUser, int, error) {
|
|
logger.Infof("getting s3 user %q", id)
|
|
|
|
// note: err is set for non-existent user but result output is also empty
|
|
result, err := runAdminCommand(c, "user", "info", "--uid", id)
|
|
if strings.Contains(result, "no user info saved") {
|
|
return nil, RGWErrorNotFound, errors.New("warn: s3 user not found")
|
|
}
|
|
if err != nil {
|
|
return nil, RGWErrorUnknown, errors.Wrapf(err, "radosgw-admin command err. %s", result)
|
|
}
|
|
return decodeUser(result)
|
|
}
|
|
|
|
// CreateUser creates a new user with the information given.
|
|
func CreateUser(c *Context, user ObjectUser) (*ObjectUser, int, error) {
|
|
logger.Debugf("creating s3 user %q", user.UserID)
|
|
|
|
if strings.TrimSpace(user.UserID) == "" {
|
|
return nil, RGWErrorBadData, errors.New("userId cannot be empty")
|
|
}
|
|
|
|
if user.DisplayName == nil {
|
|
return nil, RGWErrorBadData, errors.New("displayName is required")
|
|
}
|
|
|
|
args := []string{
|
|
"user",
|
|
"create",
|
|
"--uid", user.UserID,
|
|
"--display-name", *user.DisplayName,
|
|
}
|
|
|
|
if user.Email != nil {
|
|
args = append(args, "--email", *user.Email)
|
|
}
|
|
|
|
result, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
if strings.Contains(result, "could not create user: unable to create user, user: ") {
|
|
return nil, ErrorCodeFileExists, errors.New("s3 user already exists")
|
|
}
|
|
|
|
if strings.Contains(result, "could not create user: unable to create user, email: ") && strings.Contains(result, " is the email address an existing user") {
|
|
return nil, RGWErrorBadData, errors.New("email already in use")
|
|
}
|
|
|
|
// We don't know what happened
|
|
return nil, RGWErrorUnknown, errors.Wrap(err, "failed to create s3 user")
|
|
}
|
|
|
|
return decodeUser(result)
|
|
}
|
|
|
|
// UpdateUser updates the user whose ID matches the user.
|
|
func UpdateUser(c *Context, user ObjectUser) (*ObjectUser, int, error) {
|
|
logger.Infof("updating s3 user %q", user.UserID)
|
|
|
|
args := []string{"user", "modify", "--uid", user.UserID}
|
|
|
|
if user.DisplayName != nil {
|
|
args = append(args, "--display-name", *user.DisplayName)
|
|
}
|
|
if user.Email != nil {
|
|
args = append(args, "--email", *user.Email)
|
|
}
|
|
|
|
body, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
return nil, RGWErrorUnknown, errors.Wrap(err, "failed to update s3 user")
|
|
}
|
|
|
|
if body == "could not modify user: unable to modify user, user not found" {
|
|
return nil, RGWErrorNotFound, errors.New("s3 user not found")
|
|
}
|
|
|
|
return decodeUser(body)
|
|
}
|
|
|
|
// DeleteUser deletes the user with the given ID.
|
|
func DeleteUser(c *Context, id string, opts ...string) (string, error) {
|
|
args := []string{"user", "rm", "--uid", id}
|
|
if opts != nil {
|
|
args = append(args, opts...)
|
|
}
|
|
result, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
// If User does not exist return success
|
|
if code, ok := exec.ExitStatus(err); ok && code == int(syscall.ENOENT) {
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
return result, errors.Wrap(err, "failed to delete s3 user")
|
|
}
|
|
|
|
func SetQuotaUserBucketMax(c *Context, id string, max int) (string, int, error) {
|
|
logger.Infof("Setting user %q max buckets to %d", id, max)
|
|
args := []string{"--quota-scope", "user", "--max-buckets", strconv.Itoa(max)}
|
|
result, errCode, err := setUserQuota(c, id, args)
|
|
if errCode != RGWErrorNone {
|
|
err = errors.Wrap(err, "failed setting bucket max")
|
|
}
|
|
return result, errCode, err
|
|
}
|
|
|
|
func setUserQuota(c *Context, id string, args []string) (string, int, error) {
|
|
args = append([]string{"quota", "set", "--uid", id}, args...)
|
|
result, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "failed to set max buckets for user")
|
|
}
|
|
return result, RGWErrorNone, err
|
|
}
|
|
|
|
func LinkUser(c *Context, id, bucket string) (string, int, error) {
|
|
logger.Infof("Linking (user: %s) (bucket: %s)", id, bucket)
|
|
args := []string{"bucket", "link", "--uid", id, "--bucket", bucket}
|
|
result, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
return "", RGWErrorUnknown, err
|
|
}
|
|
if strings.Contains(result, "bucket entry point user mismatch") {
|
|
return "", RGWErrorNotFound, err
|
|
}
|
|
return result, RGWErrorNone, nil
|
|
}
|
|
|
|
func UnlinkUser(c *Context, id, bucket string) (string, int, error) {
|
|
logger.Infof("Unlinking (user: %s) (bucket: %s)", id, bucket)
|
|
args := []string{"bucket", "unlink", "--uid", id, "--bucket", bucket}
|
|
result, err := runAdminCommand(c, args...)
|
|
if err != nil {
|
|
return "", RGWErrorUnknown, err
|
|
}
|
|
if strings.Contains(result, "bucket entry point user mismatch") {
|
|
return "", RGWErrorNotFound, err
|
|
}
|
|
return result, RGWErrorNone, nil
|
|
}
|