Skip to content

Commit

Permalink
Add Ceph RBD driver
Browse files Browse the repository at this point in the history
This patch introduces the Ceph RBD driver
  • Loading branch information
codenrhoden committed Dec 15, 2016
1 parent b30a8fd commit 0ac1099
Show file tree
Hide file tree
Showing 19 changed files with 1,885 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .docs/user-guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ remote storage systems. Currently the following storage drivers are supported:
[VirtualBox](./storage-providers.md#virtualbox) | virtualbox
[EBS](./storage-providers.md#aws-ebs) | ebs, ec2
[EFS](./storage-providers.md#aws-efs) | efs
[RBD](./storage-providers.md#ceph-rbd) | rbd
..more coming|

The `libstorage.server.libstorage.storage.driver` property can be used to
Expand Down Expand Up @@ -694,6 +695,7 @@ ScaleIO|Yes
VirtualBox|Yes
EBS|Yes
EFS|No
RBD|No

#### Ignore Used Count
By default accounting takes place during operations that are performed
Expand Down
86 changes: 86 additions & 0 deletions .docs/user-guide/storage-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,89 @@ libstorage:
region: us-east-1
tag: test
```
## Ceph RBD
The Ceph RBD driver registers a driver named `rbd` with the `libStorage` driver
manager and is used to connect and mount RADOS Block Devices from a Ceph
cluster.

### Requirements

* The `ceph` and `rbd` binary executables must be installed on the host
* The `rbd` kernel module must be installed
* A `ceph.conf` file must be present in its default location
(`/etc/ceph/ceph.conf`)
* The ceph `admin` key must be present in `/etc/ceph/`

### Configuration
The following is an example with all possible fields configured. For a running
example see the `Examples` section.

```yaml
rbd:
defaultPool: rbd
```

#### Configuration Notes

* The `defaultPool` parameter is optional, and defaults to "rbd". When set, all
volume requests that do not reference a specific pool will use the
`defaultPool` value as the destination storage pool.

### Runtime behavior

The Ceph RBD driver only works when the client and server are on the same node.
There is no way for a centralized `libStorage` server to attach volumes to
clients, therefore the `libStorage` server must be running on each node that
wishes to mount RBD volumes.

The RBD driver uses the format of `<pool>.<name>` for the volume ID. This allows
for the use of multiple pools by the driver. During a volume create, if the
volume ID is given as `<pool>.<name>`, a volume named *name* will be created in
the *pool* storage pool. If no pool is referenced, the `defaultPool` will be
used.

When querying volumes, the driver will return all RBDs present in all pools in
the cluster, prefixing each volume with the appropriate `<pool>.` value.

All RBD creates are done using the default 4MB object size, and using the
"layering" feature bit to ensure greatest compatibility with the kernel clients.

### Activating the Driver
To activate the Ceph RBD driver please follow the instructions for
[activating storage drivers](./config.md#storage-drivers), using `rbd` as the
driver name.

### Troubleshooting

* Make sure that `ceph` and `rbd` commands work without extra parameters for
ID, key, and monitors. All configuration must come from `ceph.conf`.
* Check status of the ceph cluster with `ceph -s` command.

### Examples

Below is a full `config.yml` that works with RBD

```yaml
libstorage:
server:
services:
rbd:
driver: rbd
rbd:
defaultPool: rbd
```

### Caveats

* Snapshot and copy functionality is not yet implemented
* libStorage Server must be running on each host to mount/attach RBD volumes
* There is not yet options for using non-admin cephx keys or changing RBD create
features
* Volume pre-emption is not supported. Ceph does not provide a method to
forcefully detach a volume from a remote host -- only a host can attach and
detach volumes from itself.
* RBD advisory locks are not yet in use. A volume is returned as "unavailable"
if it has a watcher other than the requesting client. Until advisory locks are
in place, it may be possible for a client to attach a volume that is already
attached to another node. Mounting and writing to such a volume could lead to
data corruption.
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ BUILD_TAGS += libstorage_storage_driver \
endif
endif

RBD_BUILD_TAGS := gofig \
pflag \
libstorage_integration_driver_docker \
libstorage_storage_driver \
libstorage_storage_driver_rbd \
libstorage_storage_executor \
libstorage_storage_executor_rbd

all:
# if docker is running, then let's use docker to build it
ifneq (,$(shell if docker version &> /dev/null; then echo -; fi))
Expand Down Expand Up @@ -1062,6 +1070,10 @@ test:
test-debug:
env LIBSTORAGE_DEBUG=true $(MAKE) test

test-rbd:
env BUILD_TAGS="$(RBD_BUILD_TAGS)" $(MAKE) deps
env BUILD_TAGS="$(RBD_BUILD_TAGS)" $(MAKE) ./drivers/storage/rbd/tests/rbd.test

clean: $(GO_CLEAN)

clobber: clean $(GO_CLOBBER)
Expand Down
201 changes: 201 additions & 0 deletions drivers/storage/rbd/executor/rbd_executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// +build !libstorage_storage_executor libstorage_storage_executor_rbd

package executor

import (
"bufio"
"bytes"
"net"
"os/exec"
"strings"

gofig "github.com/akutz/gofig/types"
"github.com/akutz/goof"
"github.com/akutz/gotil"

"github.com/codedellemc/libstorage/api/registry"
"github.com/codedellemc/libstorage/api/types"
"github.com/codedellemc/libstorage/drivers/storage/rbd"
"github.com/codedellemc/libstorage/drivers/storage/rbd/utils"
)

type driver struct {
config gofig.Config
}

func init() {
registry.RegisterStorageExecutor(rbd.Name, newdriver)
}

func newdriver() types.StorageExecutor {
return &driver{}
}

func (d *driver) Init(context types.Context, config gofig.Config) error {
d.config = config
return nil
}

func (d *driver) Name() string {
return rbd.Name
}

func (d *driver) Supported(
ctx types.Context,
opts types.Store) (bool, error) {

if !gotil.FileExistsInPath("ceph") {
return false, nil
}

if !gotil.FileExistsInPath("rbd") {
return false, nil
}

if !gotil.FileExistsInPath("ip") {
return false, nil
}

if err := exec.Command("modprobe", "rbd").Run(); err != nil {
return false, nil
}

return true, nil
}

// NextDevice returns the next available device.
func (d *driver) NextDevice(
ctx types.Context,
opts types.Store) (string, error) {

return "", types.ErrNotImplemented
}

// LocalDevices returns a map of the system's local devices.
func (d *driver) LocalDevices(
ctx types.Context,
opts *types.LocalDevicesOpts) (*types.LocalDevices, error) {

devMap, err := utils.GetMappedRBDs()
if err != nil {
return nil, err
}

ld := &types.LocalDevices{Driver: d.Name()}
if len(devMap) > 0 {
ld.DeviceMap = devMap
}

return ld, nil
}

// InstanceID returns the local system's InstanceID.
func (d *driver) InstanceID(
ctx types.Context,
opts types.Store) (*types.InstanceID, error) {

return GetInstanceID(nil, nil)
}

// GetInstanceID returns the instance ID object
func GetInstanceID(
monIPs []net.IP,
localIntfs []net.Addr) (*types.InstanceID, error) {
/* Ceph doesn't have only one unique identifier per client, it can have
several. With the way the RBD driver is used, we will see multiple
identifiers used, and therefore returning any of those identifiers
is actually confusing rather than helpful. Instead, we use the client
IP address that is on the interface that can reach the monitors.
We loop through all the monitor IPs, looking for a local interface
that is on the same L2 segment. If these all fail, We are on an L3
segment so we grab the IP from the default route.
*/

var err error
if nil == monIPs {
monIPs, err = getCephMonIPs()
if err != nil {
return nil, err
}
}
if len(monIPs) == 0 {
return nil, goof.New("No Ceph Monitors found")
}

if nil == localIntfs {
localIntfs, err = net.InterfaceAddrs()
if err != nil {
return nil, err
}
}

iid := &types.InstanceID{Driver: rbd.Name}
for _, intf := range localIntfs {
localIP, localNet, _ := net.ParseCIDR(intf.String())
for _, monIP := range monIPs {
if localNet.Contains(monIP) {
// Monitor reachable over L2
iid.ID = localIP.String()
return iid, nil
}
}
}

// No luck finding L2 match, check for default/static route to monitor
localIP, err := getSrcIP(monIPs[0].String())
if err != nil {
return nil, err
}
iid.ID = localIP

return iid, nil
}

func getCephMonIPs() ([]net.IP, error) {
out, err := exec.Command("ceph-conf", "--lookup", "mon_host").Output()
if err != nil {
return nil, goof.WithError("Unable to get Ceph monitors", err)
}

monStrings := strings.Split(strings.TrimSpace(string(out)), ",")

monIps := make([]net.IP, 0, 4)

for _, mon := range monStrings {
ip := net.ParseIP(mon)
if ip != nil {
monIps = append(monIps, ip)
} else {
ipSlice, err := net.LookupIP(mon)
if err == nil {
monIps = append(monIps, ipSlice...)
}
}
}

return monIps, nil
}

func getSrcIP(destIP string) (string, error) {
out, err := exec.Command(
"ip", "-oneline", "route", "get", destIP).Output()
if err != nil {
return "", goof.WithError("Unable get IP routes", err)
}

byteReader := bytes.NewReader(out)
scanner := bufio.NewScanner(byteReader)
scanner.Split(bufio.ScanWords)
found := false
for scanner.Scan() {
if !found {
if scanner.Text() == "src" {
found = true
continue
}
}
return scanner.Text(), nil
}
return "", goof.New("Unable to parse ip output")
}
23 changes: 23 additions & 0 deletions drivers/storage/rbd/rbd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// +build !libstorage_storage_driver libstorage_storage_driver_rbd

package rbd

import (
gofigCore "github.com/akutz/gofig"
gofig "github.com/akutz/gofig/types"
)

const (
// Name is the name of the storage driver
Name = "rbd"
)

func init() {
registerConfig()
}

func registerConfig() {
r := gofigCore.NewRegistration("RBD")
r.Key(gofig.String, "", "rbd", "", "rbd.defaultPool")
gofigCore.Register(r)
}
Loading

0 comments on commit 0ac1099

Please sign in to comment.