Commit 836ac6e4 authored by Humble Chirammal's avatar Humble Chirammal

GlusterFS dynamic provisioner and deleter interface based on StorageClass claims

parent 0bef4243
...@@ -1374,6 +1374,21 @@ ...@@ -1374,6 +1374,21 @@
"Rev": "1d46ce7e1eca635f372357a8ccbf1fa7cc28b7d2" "Rev": "1d46ce7e1eca635f372357a8ccbf1fa7cc28b7d2"
}, },
{ {
"ImportPath": "github.com/heketi/heketi/client/api/go-client",
"Comment": "v2.1.0-dev-2-gcb07059",
"Rev": "cb07059aed8760ef857a58d37598b9e51c36a9ae"
},
{
"ImportPath": "github.com/heketi/heketi/pkg/glusterfs/api",
"Comment": "v2.1.0-dev-2-gcb07059",
"Rev": "cb07059aed8760ef857a58d37598b9e51c36a9ae"
},
{
"ImportPath": "github.com/heketi/heketi/pkg/utils",
"Comment": "v2.1.0-dev-2-gcb07059",
"Rev": "cb07059aed8760ef857a58d37598b9e51c36a9ae"
},
{
"ImportPath": "github.com/imdario/mergo", "ImportPath": "github.com/imdario/mergo",
"Comment": "0.1.3-8-g6633656", "Comment": "0.1.3-8-g6633656",
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc" "Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
...@@ -1445,6 +1460,11 @@ ...@@ -1445,6 +1460,11 @@
"Rev": "f7ee69f31298ecbe5d2b349c711e2547a617d398" "Rev": "f7ee69f31298ecbe5d2b349c711e2547a617d398"
}, },
{ {
"ImportPath": "github.com/lpabon/godbc",
"Comment": "v1.0-1-g9577782",
"Rev": "9577782540c1398b710ddae1b86268ba03a19b0c"
},
{
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
}, },
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
_ "k8s.io/kubernetes/pkg/cloudprovider/providers" _ "k8s.io/kubernetes/pkg/cloudprovider/providers"
// Volume plugins // Volume plugins
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
...@@ -39,11 +40,10 @@ import ( ...@@ -39,11 +40,10 @@ import (
"k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/cinder"
"k8s.io/kubernetes/pkg/volume/flexvolume" "k8s.io/kubernetes/pkg/volume/flexvolume"
"k8s.io/kubernetes/pkg/volume/gce_pd" "k8s.io/kubernetes/pkg/volume/gce_pd"
"k8s.io/kubernetes/pkg/volume/glusterfs"
"k8s.io/kubernetes/pkg/volume/host_path" "k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/nfs" "k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/vsphere_volume" "k8s.io/kubernetes/pkg/volume/vsphere_volume"
"github.com/golang/glog"
) )
// ProbeAttachableVolumePlugins collects all volume plugins for the attach/ // ProbeAttachableVolumePlugins collects all volume plugins for the attach/
...@@ -97,6 +97,7 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen ...@@ -97,6 +97,7 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
glog.Fatalf("Could not create NFS recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathNFS, err) glog.Fatalf("Could not create NFS recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathNFS, err)
} }
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...) allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
if cloud != nil { if cloud != nil {
switch { switch {
......
...@@ -81,6 +81,28 @@ parameters: ...@@ -81,6 +81,28 @@ parameters:
* `type`: `pd-standard` or `pd-ssd`. Default: `pd-ssd` * `type`: `pd-standard` or `pd-ssd`. Default: `pd-ssd`
* `zone`: GCE zone. If not specified, a random zone in the same region as controller-manager will be chosen. * `zone`: GCE zone. If not specified, a random zone in the same region as controller-manager will be chosen.
#### GLUSTERFS
```yaml
apiVersion: extensions/v1beta1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
endpoint: "glusterfs-cluster"
resturl: "http://127.0.0.1:8081"
restauthenabled: "true"
restuser: "admin"
restuserkey: "password"
```
* `endpoint`: `glusterfs-cluster` is the endpoint/service name which includes GlusterFS trusted pool IP addresses and this parameter is mandatory.
* `resturl` : Gluster REST service url which provision gluster volumes on demand. The format should be `IPaddress:Port` and this is a mandatory parameter for GlusterFS dynamic provisioner.
* `restauthenabled` : Gluster REST service authentication boolean is required if the authentication is enabled on the REST server. If this value is 'true', 'restuser' and 'restuserkey' have to be filled.
* `restuser` : Gluster REST service user who has access to create volumes in the Gluster Trusted Pool.
* `restuserkey` : Gluster REST service user's password which will be used for authentication to the REST server.
### User provisioning requests ### User provisioning requests
Users request dynamically provisioned storage by including a storage class in their `PersistentVolumeClaim`. Users request dynamically provisioned storage by including a storage class in their `PersistentVolumeClaim`.
......
apiVersion: extensions/v1beta1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
endpoint: "glusterfs-cluster"
resturl: "http://127.0.0.1:8081"
restauthenabled: "true"
restuser: "admin"
restuserkey: "password"
\ No newline at end of file
...@@ -18,33 +18,43 @@ package glusterfs ...@@ -18,33 +18,43 @@ package glusterfs
import ( import (
"fmt" "fmt"
"os"
"path"
"github.com/golang/glog" "github.com/golang/glog"
gcli "github.com/heketi/heketi/client/api/go-client"
gapi "github.com/heketi/heketi/pkg/glusterfs/api"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/exec" "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"os"
"path"
"strconv"
dstrings "strings"
) )
// This is the primary entrypoint for volume plugins. // This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin { func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&glusterfsPlugin{nil, exec.New()}} return []volume.VolumePlugin{&glusterfsPlugin{nil, exec.New(), new(glusterfsClusterConf)}}
} }
type glusterfsPlugin struct { type glusterfsPlugin struct {
host volume.VolumeHost host volume.VolumeHost
exe exec.Interface exe exec.Interface
clusterconf *glusterfsClusterConf
} }
var _ volume.VolumePlugin = &glusterfsPlugin{} var _ volume.VolumePlugin = &glusterfsPlugin{}
var _ volume.PersistentVolumePlugin = &glusterfsPlugin{} var _ volume.PersistentVolumePlugin = &glusterfsPlugin{}
var _ volume.DeletableVolumePlugin = &glusterfsPlugin{}
var _ volume.ProvisionableVolumePlugin = &glusterfsPlugin{}
var _ volume.Provisioner = &glusterfsVolumeProvisioner{}
var _ volume.Deleter = &glusterfsVolumeDeleter{}
const ( const (
glusterfsPluginName = "kubernetes.io/glusterfs" glusterfsPluginName = "kubernetes.io/glusterfs"
volprefix = "vol_"
) )
func (plugin *glusterfsPlugin) Init(host volume.VolumeHost) error { func (plugin *glusterfsPlugin) Init(host volume.VolumeHost) error {
...@@ -315,3 +325,158 @@ func getVolumeSource( ...@@ -315,3 +325,158 @@ func getVolumeSource(
return nil, false, fmt.Errorf("Spec does not reference a Gluster volume type") return nil, false, fmt.Errorf("Spec does not reference a Gluster volume type")
} }
func (plugin *glusterfsPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
if len(options.AccessModes) == 0 {
options.AccessModes = plugin.GetAccessModes()
}
return plugin.newProvisionerInternal(options)
}
func (plugin *glusterfsPlugin) newProvisionerInternal(options volume.VolumeOptions) (volume.Provisioner, error) {
return &glusterfsVolumeProvisioner{
glusterfsMounter: &glusterfsMounter{
glusterfs: &glusterfs{
plugin: plugin,
},
},
options: options,
}, nil
}
type glusterfsClusterConf struct {
glusterep string
glusterRestvolpath string
glusterRestUrl string
glusterRestAuth bool
glusterRestUser string
glusterRestUserKey string
}
type glusterfsVolumeProvisioner struct {
*glusterfsMounter
*glusterfsClusterConf
options volume.VolumeOptions
}
func (plugin *glusterfsPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
return plugin.newDeleterInternal(spec)
}
func (plugin *glusterfsPlugin) newDeleterInternal(spec *volume.Spec) (volume.Deleter, error) {
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Glusterfs == nil {
return nil, fmt.Errorf("spec.PersistentVolumeSource.Spec.Glusterfs is nil")
}
return &glusterfsVolumeDeleter{
glusterfsMounter: &glusterfsMounter{
glusterfs: &glusterfs{
volName: spec.Name(),
plugin: plugin,
},
path: spec.PersistentVolume.Spec.Glusterfs.Path,
}}, nil
}
type glusterfsVolumeDeleter struct {
*glusterfsMounter
*glusterfsClusterConf
}
func (d *glusterfsVolumeDeleter) GetPath() string {
name := glusterfsPluginName
return d.plugin.host.GetPodVolumeDir(d.glusterfsMounter.glusterfs.pod.UID, strings.EscapeQualifiedNameForDisk(name), d.glusterfsMounter.glusterfs.volName)
}
func (d *glusterfsVolumeDeleter) Delete() error {
var err error
glog.V(2).Infof("glusterfs: delete volume :%s ", d.glusterfsMounter.path)
volumetodel := d.glusterfsMounter.path
d.glusterfsClusterConf = d.plugin.clusterconf
newvolumetodel := dstrings.TrimPrefix(volumetodel, volprefix)
cli := gcli.NewClient(d.glusterRestUrl, d.glusterRestUser, d.glusterRestUserKey)
if cli == nil {
glog.Errorf("glusterfs: failed to create gluster rest client")
return fmt.Errorf("glusterfs: failed to create gluster rest client, REST server authentication failed")
}
err = cli.VolumeDelete(newvolumetodel)
if err != nil {
glog.V(4).Infof("glusterfs: error when deleting the volume :%s", err)
return err
}
glog.V(2).Infof("glusterfs: volume %s deleted successfully", volumetodel)
return nil
}
func (r *glusterfsVolumeProvisioner) Provision() (*api.PersistentVolume, error) {
var err error
if r.options.Selector != nil {
glog.V(4).Infof("glusterfs: not able to parse your claim Selector")
return nil, fmt.Errorf("glusterfs: not able to parse your claim Selector")
}
glog.V(4).Infof("glusterfs: Provison VolumeOptions %v", r.options)
for k, v := range r.options.Parameters {
switch dstrings.ToLower(k) {
case "endpoint":
r.plugin.clusterconf.glusterep = v
case "path":
r.plugin.clusterconf.glusterRestvolpath = v
case "resturl":
r.plugin.clusterconf.glusterRestUrl = v
case "restauthenabled":
r.plugin.clusterconf.glusterRestAuth, err = strconv.ParseBool(v)
case "restuser":
r.plugin.clusterconf.glusterRestUser = v
case "restuserkey":
r.plugin.clusterconf.glusterRestUserKey = v
default:
return nil, fmt.Errorf("glusterfs: invalid option %q for volume plugin %s", k, r.plugin.GetPluginName())
}
}
glog.V(4).Infof("glusterfs: storage class parameters in plugin clusterconf %v", r.plugin.clusterconf)
if !r.plugin.clusterconf.glusterRestAuth {
r.plugin.clusterconf.glusterRestUser = ""
r.plugin.clusterconf.glusterRestUserKey = ""
}
r.glusterfsClusterConf = r.plugin.clusterconf
glusterfs, sizeGB, err := r.CreateVolume()
if err != nil {
glog.Errorf("glusterfs: create volume err: %s.", err)
return nil, fmt.Errorf("glusterfs: create volume err: %s.", err)
}
pv := new(api.PersistentVolume)
pv.Spec.PersistentVolumeSource.Glusterfs = glusterfs
pv.Spec.PersistentVolumeReclaimPolicy = r.options.PersistentVolumeReclaimPolicy
pv.Spec.AccessModes = r.options.AccessModes
pv.Spec.Capacity = api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
}
return pv, nil
}
func (p *glusterfsVolumeProvisioner) CreateVolume() (r *api.GlusterfsVolumeSource, size int, err error) {
volSizeBytes := p.options.Capacity.Value()
sz := int(volume.RoundUpSize(volSizeBytes, 1024*1024*1024))
glog.V(2).Infof("glusterfs: create volume of size:%d bytes", volSizeBytes)
if p.glusterfsClusterConf.glusterRestUrl == "" {
glog.Errorf("glusterfs : rest server endpoint is empty")
return nil, 0, fmt.Errorf("failed to create gluster REST client, REST URL is empty")
}
cli := gcli.NewClient(p.glusterRestUrl, p.glusterRestUser, p.glusterRestUserKey)
if cli == nil {
glog.Errorf("glusterfs: failed to create gluster rest client")
return nil, 0, fmt.Errorf("failed to create gluster REST client, REST server authentication failed")
}
volumeReq := &gapi.VolumeCreateRequest{Size: sz}
volume, err := cli.VolumeCreate(volumeReq)
if err != nil {
glog.Errorf("glusterfs: error creating volume %s ", err)
return nil, 0, fmt.Errorf("error creating volume %v", err)
}
glog.V(1).Infof("glusterfs: volume with size :%d and name:%s created", volume.Size, volume.Name)
return &api.GlusterfsVolumeSource{
EndpointsName: p.glusterfsClusterConf.glusterep,
Path: volume.Name,
ReadOnly: false,
}, sz, nil
}
//
// Copyright (c) 2016 The heketi Authors
//
// 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 client
import (
"io"
"net/http"
"github.com/heketi/heketi/pkg/utils"
)
func (c *Client) BackupDb(w io.Writer) error {
// Create a request
req, err := http.NewRequest("GET", c.host+"/backup/db", nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
return utils.GetErrorFromResponse(r)
}
// Read data from response
defer r.Body.Close()
_, err = io.Copy(w, r.Body)
return err
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"crypto/sha256"
"encoding/hex"
"net/http"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/heketi/heketi/pkg/utils"
)
const (
MAX_CONCURRENT_REQUESTS = 32
)
// Client object
type Client struct {
host string
key string
user string
throttle chan bool
}
// Creates a new client to access a Heketi server
func NewClient(host, user, key string) *Client {
c := &Client{}
c.key = key
c.host = host
c.user = user
// Maximum concurrent requests
c.throttle = make(chan bool, MAX_CONCURRENT_REQUESTS)
return c
}
// Create a client to access a Heketi server without authentication enabled
func NewClientNoAuth(host string) *Client {
return NewClient(host, "", "")
}
// Simple Hello test to check if the server is up
func (c *Client) Hello() error {
// Create request
req, err := http.NewRequest("GET", c.host+"/hello", nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Get info
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
return utils.GetErrorFromResponse(r)
}
return nil
}
// Make sure we do not run out of fds by throttling the requests
func (c *Client) do(req *http.Request) (*http.Response, error) {
c.throttle <- true
defer func() {
<-c.throttle
}()
httpClient := &http.Client{}
httpClient.CheckRedirect = c.checkRedirect
return httpClient.Do(req)
}
// This function is called by the http package if it detects that it needs to
// be redirected. This happens when the server returns a 303 HTTP Status.
// Here we create a new token before it makes the next request.
func (c *Client) checkRedirect(req *http.Request, via []*http.Request) error {
return c.setToken(req)
}
// Wait for the job to finish, waiting waitTime on every loop
func (c *Client) waitForResponseWithTimer(r *http.Response,
waitTime time.Duration) (*http.Response, error) {
// Get temp resource
location, err := r.Location()
if err != nil {
return nil, err
}
for {
// Create request
req, err := http.NewRequest("GET", location.String(), nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Wait for response
r, err = c.do(req)
if err != nil {
return nil, err
}
// Check if the request is pending
if r.Header.Get("X-Pending") == "true" {
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
time.Sleep(waitTime)
} else {
return r, nil
}
}
}
// Create JSON Web Token
func (c *Client) setToken(r *http.Request) error {
// Create qsh hash
qshstring := r.Method + "&" + r.URL.Path
hash := sha256.New()
hash.Write([]byte(qshstring))
// Create Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
// Set issuer
"iss": c.user,
// Set issued at time
"iat": time.Now().Unix(),
// Set expiration
"exp": time.Now().Add(time.Minute * 5).Unix(),
// Set qsh
"qsh": hex.EncodeToString(hash.Sum(nil)),
})
// Sign the token
signedtoken, err := token.SignedString([]byte(c.key))
if err != nil {
return err
}
// Save it in the header
r.Header.Set("Authorization", "bearer "+signedtoken)
return nil
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"bytes"
"github.com/heketi/heketi/pkg/glusterfs/api"
"github.com/heketi/heketi/pkg/utils"
"net/http"
)
func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) {
// Create a request
req, err := http.NewRequest("POST", c.host+"/clusters", bytes.NewBuffer([]byte(`{}`)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Send request
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusCreated {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var cluster api.ClusterInfoResponse
err = utils.GetJsonFromResponse(r, &cluster)
r.Body.Close()
if err != nil {
return nil, err
}
return &cluster, nil
}
func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/clusters/"+id, nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var cluster api.ClusterInfoResponse
err = utils.GetJsonFromResponse(r, &cluster)
r.Body.Close()
if err != nil {
return nil, err
}
return &cluster, nil
}
func (c *Client) ClusterList() (*api.ClusterListResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/clusters", nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var clusters api.ClusterListResponse
err = utils.GetJsonFromResponse(r, &clusters)
if err != nil {
return nil, err
}
return &clusters, nil
}
func (c *Client) ClusterDelete(id string) error {
// Create DELETE request
req, err := http.NewRequest("DELETE", c.host+"/clusters/"+id, nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
return utils.GetErrorFromResponse(r)
}
return nil
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"bytes"
"encoding/json"
"github.com/heketi/heketi/pkg/glusterfs/api"
"github.com/heketi/heketi/pkg/utils"
"net/http"
"time"
)
func (c *Client) DeviceAdd(request *api.DeviceAddRequest) error {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return err
}
// Create a request
req, err := http.NewRequest("POST", c.host+"/devices", bytes.NewBuffer(buffer))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusAccepted {
return utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Second)
if err != nil {
return err
}
if r.StatusCode != http.StatusNoContent {
return utils.GetErrorFromResponse(r)
}
return nil
}
func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/devices/"+id, nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var device api.DeviceInfoResponse
err = utils.GetJsonFromResponse(r, &device)
r.Body.Close()
if err != nil {
return nil, err
}
return &device, nil
}
func (c *Client) DeviceDelete(id string) error {
// Create a request
req, err := http.NewRequest("DELETE", c.host+"/devices/"+id, nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusAccepted {
return utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Second)
if err != nil {
return err
}
if r.StatusCode != http.StatusNoContent {
return utils.GetErrorFromResponse(r)
}
return nil
}
func (c *Client) DeviceState(id string,
request *api.StateRequest) error {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return err
}
// Create a request
req, err := http.NewRequest("POST",
c.host+"/devices/"+id+"/state",
bytes.NewBuffer(buffer))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Get info
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
return utils.GetErrorFromResponse(r)
}
return nil
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"bytes"
"encoding/json"
"github.com/heketi/heketi/pkg/glusterfs/api"
"github.com/heketi/heketi/pkg/utils"
"net/http"
"time"
)
func (c *Client) NodeAdd(request *api.NodeAddRequest) (*api.NodeInfoResponse, error) {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return nil, err
}
// Create a request
req, err := http.NewRequest("POST", c.host+"/nodes", bytes.NewBuffer(buffer))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Send request
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusAccepted {
return nil, utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Millisecond*250)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var node api.NodeInfoResponse
err = utils.GetJsonFromResponse(r, &node)
r.Body.Close()
if err != nil {
return nil, err
}
return &node, nil
}
func (c *Client) NodeInfo(id string) (*api.NodeInfoResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/nodes/"+id, nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var node api.NodeInfoResponse
err = utils.GetJsonFromResponse(r, &node)
r.Body.Close()
if err != nil {
return nil, err
}
return &node, nil
}
func (c *Client) NodeDelete(id string) error {
// Create a request
req, err := http.NewRequest("DELETE", c.host+"/nodes/"+id, nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusAccepted {
return utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Millisecond*250)
if err != nil {
return err
}
if r.StatusCode != http.StatusNoContent {
return utils.GetErrorFromResponse(r)
}
return nil
}
func (c *Client) NodeState(id string, request *api.StateRequest) error {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return err
}
// Create a request
req, err := http.NewRequest("POST",
c.host+"/nodes/"+id+"/state",
bytes.NewBuffer(buffer))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Get info
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
return utils.GetErrorFromResponse(r)
}
return nil
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"github.com/heketi/heketi/pkg/glusterfs/api"
)
func (c *Client) TopologyInfo() (*api.TopologyInfoResponse, error) {
topo := &api.TopologyInfoResponse{
ClusterList: make([]api.Cluster, 0),
}
clusterlist, err := c.ClusterList()
if err != nil {
return nil, err
}
for _, cluster := range clusterlist.Clusters {
clusteri, err := c.ClusterInfo(cluster)
if err != nil {
return nil, err
}
cluster := api.Cluster{
Id: clusteri.Id,
Volumes: make([]api.VolumeInfoResponse, 0),
Nodes: make([]api.NodeInfoResponse, 0),
}
cluster.Id = clusteri.Id
// Iterate over the volume list in the cluster
for _, volumes := range clusteri.Volumes {
volumesi, err := c.VolumeInfo(volumes)
if err != nil {
return nil, err
}
if volumesi.Cluster == cluster.Id {
cluster.Volumes = append(cluster.Volumes, *volumesi)
}
}
// Iterate over the nodes in the cluster
for _, node := range clusteri.Nodes {
nodei, err := c.NodeInfo(string(node))
if err != nil {
return nil, err
}
cluster.Nodes = append(cluster.Nodes, *nodei)
}
topo.ClusterList = append(topo.ClusterList, cluster)
}
return topo, nil
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 client
import (
"bytes"
"encoding/json"
"net/http"
"time"
"github.com/heketi/heketi/pkg/glusterfs/api"
"github.com/heketi/heketi/pkg/utils"
)
func (c *Client) VolumeCreate(request *api.VolumeCreateRequest) (
*api.VolumeInfoResponse, error) {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return nil, err
}
// Create a request
req, err := http.NewRequest("POST",
c.host+"/volumes",
bytes.NewBuffer(buffer))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Send request
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusAccepted {
return nil, utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Second)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var volume api.VolumeInfoResponse
err = utils.GetJsonFromResponse(r, &volume)
r.Body.Close()
if err != nil {
return nil, err
}
return &volume, nil
}
func (c *Client) VolumeExpand(id string, request *api.VolumeExpandRequest) (
*api.VolumeInfoResponse, error) {
// Marshal request to JSON
buffer, err := json.Marshal(request)
if err != nil {
return nil, err
}
// Create a request
req, err := http.NewRequest("POST",
c.host+"/volumes/"+id+"/expand",
bytes.NewBuffer(buffer))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Send request
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusAccepted {
return nil, utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Second)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var volume api.VolumeInfoResponse
err = utils.GetJsonFromResponse(r, &volume)
r.Body.Close()
if err != nil {
return nil, err
}
return &volume, nil
}
func (c *Client) VolumeList() (*api.VolumeListResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/volumes", nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var volumes api.VolumeListResponse
err = utils.GetJsonFromResponse(r, &volumes)
if err != nil {
return nil, err
}
return &volumes, nil
}
func (c *Client) VolumeInfo(id string) (*api.VolumeInfoResponse, error) {
// Create request
req, err := http.NewRequest("GET", c.host+"/volumes/"+id, nil)
if err != nil {
return nil, err
}
// Set token
err = c.setToken(req)
if err != nil {
return nil, err
}
// Get info
r, err := c.do(req)
if err != nil {
return nil, err
}
if r.StatusCode != http.StatusOK {
return nil, utils.GetErrorFromResponse(r)
}
// Read JSON response
var volume api.VolumeInfoResponse
err = utils.GetJsonFromResponse(r, &volume)
r.Body.Close()
if err != nil {
return nil, err
}
return &volume, nil
}
func (c *Client) VolumeDelete(id string) error {
// Create a request
req, err := http.NewRequest("DELETE", c.host+"/volumes/"+id, nil)
if err != nil {
return err
}
// Set token
err = c.setToken(req)
if err != nil {
return err
}
// Send request
r, err := c.do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusAccepted {
return utils.GetErrorFromResponse(r)
}
// Wait for response
r, err = c.waitForResponseWithTimer(r, time.Second)
if err != nil {
return err
}
if r.StatusCode != http.StatusNoContent {
return utils.GetErrorFromResponse(r)
}
return nil
}
//
// Copyright (c) 2016 The heketi Authors
//
// 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.
//
//
// Please see https://github.com/heketi/heketi/wiki/API
// for documentation
//
package api
import (
"fmt"
"sort"
)
// State
type EntryState string
const (
EntryStateUnknown EntryState = ""
EntryStateOnline EntryState = "online"
EntryStateOffline EntryState = "offline"
EntryStateFailed EntryState = "failed"
)
type DurabilityType string
const (
DurabilityReplicate DurabilityType = "replicate"
DurabilityDistributeOnly DurabilityType = "none"
DurabilityEC DurabilityType = "disperse"
)
// Common
type StateRequest struct {
State EntryState `json:"state"`
}
// Storage values in KB
type StorageSize struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
}
type HostAddresses struct {
Manage sort.StringSlice `json:"manage"`
Storage sort.StringSlice `json:"storage"`
}
// Brick
type BrickInfo struct {
Id string `json:"id"`
Path string `json:"path"`
DeviceId string `json:"device"`
NodeId string `json:"node"`
// Size in KB
Size uint64 `json:"size"`
}
// Device
type Device struct {
Name string `json:"name"`
}
type DeviceAddRequest struct {
Device
NodeId string `json:"node"`
}
type DeviceInfo struct {
Device
Storage StorageSize `json:"storage"`
Id string `json:"id"`
}
type DeviceInfoResponse struct {
DeviceInfo
State EntryState `json:"state"`
Bricks []BrickInfo `json:"bricks"`
}
// Node
type NodeAddRequest struct {
Zone int `json:"zone"`
Hostnames HostAddresses `json:"hostnames"`
ClusterId string `json:"cluster"`
}
type NodeInfo struct {
NodeAddRequest
Id string `json:"id"`
}
type NodeInfoResponse struct {
NodeInfo
State EntryState `json:"state"`
DevicesInfo []DeviceInfoResponse `json:"devices"`
}
// Cluster
type Cluster struct {
Volumes []VolumeInfoResponse `json:"volumes"`
Nodes []NodeInfoResponse `json:"nodes"`
Id string `json:"id"`
}
type TopologyInfoResponse struct {
ClusterList []Cluster `json:"clusters"`
}
type ClusterInfoResponse struct {
Id string `json:"id"`
Nodes sort.StringSlice `json:"nodes"`
Volumes sort.StringSlice `json:"volumes"`
}
type ClusterListResponse struct {
Clusters []string `json:"clusters"`
}
// Durabilities
type ReplicaDurability struct {
Replica int `json:"replica,omitempty"`
}
type DisperseDurability struct {
Data int `json:"data,omitempty"`
Redundancy int `json:"redundancy,omitempty"`
}
// Volume
type VolumeDurabilityInfo struct {
Type DurabilityType `json:"type,omitempty"`
Replicate ReplicaDurability `json:"replicate,omitempty"`
Disperse DisperseDurability `json:"disperse,omitempty"`
}
type VolumeCreateRequest struct {
// Size in GB
Size int `json:"size"`
Clusters []string `json:"clusters,omitempty"`
Name string `json:"name"`
Durability VolumeDurabilityInfo `json:"durability,omitempty"`
Snapshot struct {
Enable bool `json:"enable"`
Factor float32 `json:"factor"`
} `json:"snapshot"`
}
type VolumeInfo struct {
VolumeCreateRequest
Id string `json:"id"`
Cluster string `json:"cluster"`
Mount struct {
GlusterFS struct {
Hosts []string `json:"hosts"`
MountPoint string `json:"device"`
Options map[string]string `json:"options"`
} `json:"glusterfs"`
} `json:"mount"`
}
type VolumeInfoResponse struct {
VolumeInfo
Bricks []BrickInfo `json:"bricks"`
}
type VolumeListResponse struct {
Volumes []string `json:"volumes"`
}
type VolumeExpandRequest struct {
Size int `json:"expand_size"`
}
// Constructors
func NewVolumeInfoResponse() *VolumeInfoResponse {
info := &VolumeInfoResponse{}
info.Mount.GlusterFS.Options = make(map[string]string)
info.Bricks = make([]BrickInfo, 0)
return info
}
// String functions
func (v *VolumeInfoResponse) String() string {
s := fmt.Sprintf("Name: %v\n"+
"Size: %v\n"+
"Volume Id: %v\n"+
"Cluster Id: %v\n"+
"Mount: %v\n"+
"Mount Options: backup-volfile-servers=%v\n"+
"Durability Type: %v\n",
v.Name,
v.Size,
v.Id,
v.Cluster,
v.Mount.GlusterFS.MountPoint,
v.Mount.GlusterFS.Options["backup-volfile-servers"],
v.Durability.Type)
switch v.Durability.Type {
case DurabilityEC:
s += fmt.Sprintf("Disperse Data: %v\n"+
"Disperse Redundancy: %v\n",
v.Durability.Disperse.Data,
v.Durability.Disperse.Redundancy)
case DurabilityReplicate:
s += fmt.Sprintf("Distributed+Replica: %v\n",
v.Durability.Replicate.Replica)
}
if v.Snapshot.Enable {
s += fmt.Sprintf("Snapshot Factor: %.2f\n",
v.Snapshot.Factor)
}
/*
s += "\nBricks:\n"
for _, b := range v.Bricks {
s += fmt.Sprintf("Id: %v\n"+
"Path: %v\n"+
"Size (GiB): %v\n"+
"Node: %v\n"+
"Device: %v\n\n",
b.Id,
b.Path,
b.Size/(1024*1024),
b.NodeId,
b.DeviceId)
}
*/
return s
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"errors"
"io"
"io/ioutil"
"net/http"
)
// Return the body from a response as a string
func GetStringFromResponse(r *http.Response) (string, error) {
body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
return "", err
}
r.Body.Close()
return string(body), nil
}
// Return the body from a response as an error
func GetErrorFromResponse(r *http.Response) error {
s, err := GetStringFromResponse(r)
if err != nil {
return err
}
return errors.New(s)
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
)
func jsonFromBody(r io.Reader, v interface{}) error {
// Check body
body, err := ioutil.ReadAll(r)
if err != nil {
return err
}
if err := json.Unmarshal(body, v); err != nil {
return err
}
return nil
}
// Unmarshal JSON from request
func GetJsonFromRequest(r *http.Request, v interface{}) error {
defer r.Body.Close()
return jsonFromBody(r.Body, v)
}
// Unmarshal JSON from response
func GetJsonFromResponse(r *http.Response, v interface{}) error {
defer r.Body.Close()
return jsonFromBody(r.Body, v)
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"fmt"
"io"
"log"
"os"
"runtime"
"strings"
"github.com/lpabon/godbc"
)
type LogLevel int
// Log levels
const (
LEVEL_NOLOG LogLevel = iota
LEVEL_CRITICAL
LEVEL_ERROR
LEVEL_WARNING
LEVEL_INFO
LEVEL_DEBUG
)
var (
stderr io.Writer = os.Stderr
stdout io.Writer = os.Stdout
)
type Logger struct {
critlog, errorlog, infolog *log.Logger
debuglog, warninglog *log.Logger
level LogLevel
}
func logWithLongFile(l *log.Logger, format string, v ...interface{}) {
_, file, line, _ := runtime.Caller(2)
// Shorten the path.
// From
// /builddir/build/BUILD/heketi-3f4a5b1b6edff87232e8b24533c53b4151ebd9c7/src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go
// to
// src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go
i := strings.Index(file, "/src/")
if i == -1 {
i = 0
}
l.Print(fmt.Sprintf("%v:%v: ", file[i:], line) +
fmt.Sprintf(format, v...))
}
// Create a new logger
func NewLogger(prefix string, level LogLevel) *Logger {
godbc.Require(level >= 0, level)
godbc.Require(level <= LEVEL_DEBUG, level)
l := &Logger{}
if level == LEVEL_NOLOG {
l.level = LEVEL_DEBUG
} else {
l.level = level
}
l.critlog = log.New(stderr, prefix+" CRITICAL ", log.LstdFlags)
l.errorlog = log.New(stderr, prefix+" ERROR ", log.LstdFlags)
l.warninglog = log.New(stdout, prefix+" WARNING ", log.LstdFlags)
l.infolog = log.New(stdout, prefix+" INFO ", log.LstdFlags)
l.debuglog = log.New(stdout, prefix+" DEBUG ", log.LstdFlags)
godbc.Ensure(l.critlog != nil)
godbc.Ensure(l.errorlog != nil)
godbc.Ensure(l.warninglog != nil)
godbc.Ensure(l.infolog != nil)
godbc.Ensure(l.debuglog != nil)
return l
}
// Return current level
func (l *Logger) Level() LogLevel {
return l.level
}
// Set level
func (l *Logger) SetLevel(level LogLevel) {
l.level = level
}
// Log critical information
func (l *Logger) Critical(format string, v ...interface{}) {
if l.level >= LEVEL_CRITICAL {
logWithLongFile(l.critlog, format, v...)
}
}
// Log error string
func (l *Logger) LogError(format string, v ...interface{}) {
if l.level >= LEVEL_ERROR {
logWithLongFile(l.errorlog, format, v...)
}
}
// Log error variable
func (l *Logger) Err(err error) error {
if l.level >= LEVEL_ERROR {
logWithLongFile(l.errorlog, "%v", err)
}
return err
}
// Log warning information
func (l *Logger) Warning(format string, v ...interface{}) {
if l.level >= LEVEL_WARNING {
l.warninglog.Printf(format, v...)
}
}
// Log string
func (l *Logger) Info(format string, v ...interface{}) {
if l.level >= LEVEL_INFO {
l.infolog.Printf(format, v...)
}
}
// Log string as debug
func (l *Logger) Debug(format string, v ...interface{}) {
if l.level >= LEVEL_DEBUG {
logWithLongFile(l.debuglog, format, v...)
}
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"sort"
)
// Check if a sorted string list has a string
func SortedStringHas(s sort.StringSlice, x string) bool {
index := s.Search(x)
if index == len(s) {
return false
}
return s[s.Search(x)] == x
}
// Delete a string from a sorted string list
func SortedStringsDelete(s sort.StringSlice, x string) sort.StringSlice {
index := s.Search(x)
if len(s) != index && s[index] == x {
s = append(s[:index], s[index+1:]...)
}
return s
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"sync"
)
type StatusGroup struct {
wg sync.WaitGroup
results chan error
err error
}
// Create a new goroutine error status collector
func NewStatusGroup() *StatusGroup {
s := &StatusGroup{}
s.results = make(chan error, 1)
return s
}
// Adds to the number of goroutines it should wait
func (s *StatusGroup) Add(delta int) {
s.wg.Add(delta)
}
// Removes the number of pending goroutines by one
func (s *StatusGroup) Done() {
s.wg.Done()
}
// Goroutine can return an error back to caller
func (s *StatusGroup) Err(err error) {
s.results <- err
}
// Returns an error if any of the spawned goroutines
// return an error. Only the last error is saved.
// This function must be called last after the last
// s.Register() function
func (s *StatusGroup) Result() error {
// This goroutine will wait until all
// other privously spawned goroutines finish.
// Once they finish, it will close the channel
go func() {
s.wg.Wait()
close(s.results)
}()
// Read from the channel until close
for err := range s.results {
// Only save the last one
if err != nil {
s.err = err
}
}
return s.err
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
import (
"sort"
)
type StringSet struct {
Set sort.StringSlice
}
// Create a string set.
//
// A string set is a list where each element appears only once
func NewStringSet() *StringSet {
return &StringSet{
Set: make(sort.StringSlice, 0),
}
}
// Add a string to the string set
func (s *StringSet) Add(v string) {
if !SortedStringHas(s.Set, v) {
s.Set = append(s.Set, v)
s.Set.Sort()
}
}
// Return string list
func (s *StringSet) Strings() []string {
return s.Set
}
func (s *StringSet) Len() int {
return len(s.Set)
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
type StringStack struct {
list []string
}
func NewStringStack() *StringStack {
a := &StringStack{}
a.list = make([]string, 0)
return a
}
func (a *StringStack) IsEmpty() bool {
return len(a.list) == 0
}
func (a *StringStack) Pop() (x string) {
x, a.list = a.list[0], a.list[1:len(a.list)]
return
}
func (a *StringStack) Push(x string) {
a.list = append(a.list, x)
}
//
// Copyright (c) 2015 The heketi Authors
//
// 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 utils
// From http://www.ashishbanerjee.com/home/go/go-generate-uuid
import (
"crypto/rand"
"encoding/hex"
"github.com/lpabon/godbc"
)
// Return a 16-byte uuid
func GenUUID() string {
uuid := make([]byte, 16)
n, err := rand.Read(uuid)
godbc.Check(n == len(uuid), n, len(uuid))
godbc.Check(err == nil, err)
return hex.EncodeToString(uuid)
}
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
language: go
go:
- 1.1
- 1.2
- tip
install:
- go get github.com/stretchr/testify
script:
- go test
- go test -tags 'prod'
lpabon@redhat.com
\ No newline at end of file
[![Build Status](https://travis-ci.org/lpabon/godbc.svg?branch=master)](https://travis-ci.org/lpabon/godbc)
# godbc
Design by contract for Go
# Installation
To install godbc, use `go get`:
go get github.com/lpabon/godbc
Import the `godbc` package into your code using this template:
import (
"github.com/lpabon/godbc"
)
# Documentation
Documentation is available at https://godoc.org/github.com/lpabon/godbc
//+build !prod
//
// Copyright (c) 2014 The godbc Authors
//
// 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.
// Design-by-Contract for Go
//
// Design by Contract is a programming methodology
// which binds the caller and the function called to a
// contract. The contract is represented using Hoare Triple:
// {P} C {Q}
// where {P} is the precondition before executing command C,
// and {Q} is the postcondition.
//
// See Also
//
// * http://en.wikipedia.org/wiki/Design_by_contract
// * http://en.wikipedia.org/wiki/Hoare_logic
// * http://dlang.org/dbc.html
//
// Usage
//
// Godbc is enabled by default, but can be disabled for production
// builds by using the tag 'prod' in builds and tests as follows:
// go build -tags 'prod'
// or
// go test -tags 'prod'
//
package godbc
import (
"errors"
"fmt"
"runtime"
)
// InvariantSimpleTester is an interface which provides a receiver to
// test the object
type InvariantSimpleTester interface {
Invariant() bool
}
// InvariantTester is an interface which provides not only an Invariant(),
// but also a receiver to print the structure
type InvariantTester interface {
InvariantSimpleTester
String() string
}
// dbc_panic prints to the screen information of the failure followed
// by a call to panic()
func dbc_panic(dbc_func_name string, b bool, message ...interface{}) {
if !b {
// Get caller information which is the caller
// of the caller of this function
pc, file, line, _ := runtime.Caller(2)
caller_func_info := runtime.FuncForPC(pc)
error_string := fmt.Sprintf("%s:\n\r\tfunc (%s) 0x%x\n\r\tFile %s:%d",
dbc_func_name,
caller_func_info.Name(),
pc,
file,
line)
if len(message) > 0 {
error_string += fmt.Sprintf("\n\r\tInfo: %+v", message)
}
err := errors.New(error_string)
// Finally panic
panic(err)
}
}
// Require checks that the preconditions are satisfied before
// executing the function
//
// Example Code
//
// func Divide(a, b int) int {
// godbc.Require(b != 0)
// return a/b
// }
//
func Require(b bool, message ...interface{}) {
dbc_panic("REQUIRE", b, message...)
}
// Ensure checks the postconditions are satisfied before returning
// to the caller.
//
// Example Code
//
// type Data struct {
// a int
// }
//
// func (*d Data) Set(a int) {
// d.a = a
// godbc.Ensure(d.a == a)
// }
//
func Ensure(b bool, message ...interface{}) {
dbc_panic("ENSURE", b, message...)
}
// Check provides a simple assert
func Check(b bool, message ...interface{}) {
dbc_panic("CHECK", b, message...)
}
// InvariantSimple calls the objects Invariant() receiver to test
// the object for correctness.
//
// The caller object must provide an object that supports the
// interface InvariantSimpleTester and does not need to provide
// a String() receiver
func InvariantSimple(obj InvariantSimpleTester, message ...interface{}) {
dbc_panic("INVARIANT", obj.Invariant(), message...)
}
// Invariant calls the objects Invariant() receiver to test
// the object for correctness.
//
// The caller object must provide an object that supports the
// interface InvariantTester
//
// To see an example, please take a look at the godbc_test.go
func Invariant(obj InvariantTester, message ...interface{}) {
m := append(message, obj)
dbc_panic("INVARIANT", obj.Invariant(), m)
}
//+build prod
//
// Copyright (c) 2014 The godbc Authors
//
// 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 godbc
type InvariantSimpleTester interface {
Invariant() bool
}
type InvariantTester interface {
InvariantSimpleTester
String() string
}
func Require(b bool, message ...interface{}) {
}
func Ensure(b bool, message ...interface{}) {
}
func Check(b bool, message ...interface{}) {
}
func InvariantSimple(obj InvariantSimpleTester, message ...interface{}) {
}
func Invariant(obj InvariantTester, message ...interface{}) {
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment