├── client ├── api │ ├── python │ │ ├── setup.cfg │ │ ├── requirements.txt │ │ ├── test-requirements.txt │ │ ├── heketi │ │ │ └── __init__.py │ │ ├── unittests.sh │ │ ├── tox.ini │ │ ├── test │ │ │ └── unit │ │ │ │ └── heketi.json │ │ └── setup.py │ └── go-client │ │ ├── backup.go │ │ ├── topology.go │ │ ├── cluster.go │ │ ├── device.go │ │ └── node.go └── cli │ └── go │ ├── main.go │ ├── Makefile │ ├── cmds │ └── root.go │ └── topology-sample.json ├── apps ├── doc.go ├── glusterfs │ ├── limits.go │ ├── entry.go │ ├── volume_durability.go │ ├── testapp_mock.go │ ├── entry_test.go │ ├── volume_durability_none.go │ ├── errors.go │ ├── allocator.go │ ├── app_config.go │ ├── volume_durability_replica.go │ ├── brick_create.go │ ├── dbentry_test.go │ ├── volume_durability_ec.go │ ├── app_middleware.go │ ├── volume_entry_create.go │ ├── dbentry.go │ ├── allocator_mock.go │ ├── cluster_entry.go │ └── app_cluster.go └── app.go ├── tests └── functional │ ├── TestSmokeTest │ ├── vagrant │ │ ├── up.sh │ │ ├── site.yml │ │ ├── roles │ │ │ ├── gluster │ │ │ │ └── tasks │ │ │ │ │ └── main.yml │ │ │ └── common │ │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ │ └── files │ │ │ │ └── insecure_private_key │ │ └── Vagrantfile │ ├── teardown.sh │ ├── run.sh │ ├── config │ │ ├── heketi.json │ │ └── insecure_private_key │ └── README.md │ ├── TestManyBricksVolume │ ├── vagrant │ │ ├── up.sh │ │ ├── site.yml │ │ ├── roles │ │ │ ├── gluster │ │ │ │ └── tasks │ │ │ │ │ └── main.yml │ │ │ └── common │ │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ │ └── files │ │ │ │ └── insecure_private_key │ │ └── Vagrantfile │ ├── run.sh │ ├── teardown.sh │ ├── config │ │ ├── heketi.json │ │ └── insecure_private_key │ └── README.md │ ├── TestVolumeSnapshotBehavior │ ├── vagrant │ │ ├── up.sh │ │ ├── site.yml │ │ ├── roles │ │ │ ├── gluster │ │ │ │ └── tasks │ │ │ │ │ └── main.yml │ │ │ └── common │ │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ │ └── files │ │ │ │ └── insecure_private_key │ │ └── Vagrantfile │ ├── teardown.sh │ ├── run.sh │ ├── config │ │ ├── heketi.json │ │ └── insecure_private_key │ └── README.md │ ├── TestVolumeNotDeletedWhenNodeIsDown │ ├── vagrant │ │ ├── up.sh │ │ ├── site.yml │ │ ├── roles │ │ │ ├── gluster │ │ │ │ └── tasks │ │ │ │ │ └── main.yml │ │ │ └── common │ │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ │ └── files │ │ │ │ └── insecure_private_key │ │ └── Vagrantfile │ ├── run.sh │ ├── teardown.sh │ └── config │ │ ├── heketi.json │ │ └── insecure_private_key │ ├── TestKubeSmokeTest │ ├── storageclass.yaml.sed │ ├── md5sums │ ├── teardown.sh │ ├── mock-endpoints.json │ ├── pvc.json │ ├── mock-topology.json │ └── testHeketiMock.sh │ ├── README.md │ ├── run.sh │ └── lib.sh ├── doc └── design │ └── README.md ├── AUTHORS ├── extras ├── docker │ ├── ci │ │ ├── README.md │ │ ├── Dockerfile │ │ └── heketi.json │ ├── gluster │ │ ├── gluster-setup.service │ │ ├── README.md │ │ ├── gluster-setup.sh │ │ └── Dockerfile │ ├── rpi │ │ ├── README.md │ │ ├── Dockerfile │ │ ├── build-rpi-dockerfile.sh │ │ ├── heketi-start.sh │ │ └── heketi.json │ └── fromsource │ │ ├── heketi-start.sh │ │ ├── heketi.json │ │ ├── Dockerfile │ │ └── README.md ├── openshift │ ├── service │ │ └── sample-gluster-service.json │ ├── endpoint │ │ └── sample-gluster-endpoint.json │ └── templates │ │ └── README.md ├── systemd │ └── heketi.service ├── kubernetes │ ├── heketi-start.sh │ ├── README.md │ └── heketi-deployment.json └── etc │ └── init │ └── heketi.initd ├── LICENSE ├── .travis-fork-fix ├── pkg ├── db │ └── dbvolume.go ├── utils │ ├── uuid.go │ ├── stringstack.go │ ├── sortedstrings.go │ ├── bodystring.go │ ├── stringset.go │ ├── jsonutils.go │ ├── statusgroup_test.go │ ├── stringset_test.go │ ├── statusgroup.go │ ├── sortedstrings_test.go │ └── log.go ├── kubernetes │ ├── namespace.go │ ├── pv.go │ └── backupdb.go └── heketitest │ ├── heketitest_test.go │ └── heketitest.go ├── .gitignore ├── executors ├── kubeexec │ ├── config.go │ └── kubeexec_test.go ├── sshexec │ ├── config.go │ ├── peer.go │ ├── peer_test.go │ └── device.go └── executor.go ├── glide.yaml ├── .travis-coverage ├── .travis.yml ├── etc └── heketi.json ├── README.md ├── middleware └── jwt.go └── Makefile /client/api/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 -------------------------------------------------------------------------------- /apps/doc.go: -------------------------------------------------------------------------------- 1 | // Location for applications for Heketi 2 | package apps 3 | -------------------------------------------------------------------------------- /client/api/python/requirements.txt: -------------------------------------------------------------------------------- 1 | pyjwt >= 1.4.0 2 | requests >= 2.9.0 -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vagrant up --no-provision $@ 4 | vagrant provision 5 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vagrant up --no-provision $@ 4 | vagrant provision 5 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vagrant up --no-provision $@ 4 | vagrant provision 5 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vagrant up --no-provision $@ 4 | vagrant provision 5 | -------------------------------------------------------------------------------- /doc/design/README.md: -------------------------------------------------------------------------------- 1 | # Heketi Design Documents and Proposals 2 | 3 | This directory contains Heketi design documents and accepted design proposals. 4 | -------------------------------------------------------------------------------- /client/api/python/test-requirements.txt: -------------------------------------------------------------------------------- 1 | # Hacking already pins down pep8, pyflakes and flake8 2 | hacking>=0.5.6,<0.6 3 | coverage 4 | nose 5 | nosexcover 6 | nosehtmloutput 7 | mock>=0.8.0 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | lpabon@redhat.com 2 | campbellalex11@gmail.com 3 | sid@sidcarter.com 4 | hchiramm@redhat.com 5 | mliyazud@redhat.com 6 | nerawat@redhat.com 7 | obnox@redhat.com 8 | obnox@samba.org 9 | lpabon@gmail.com 10 | -------------------------------------------------------------------------------- /extras/docker/ci/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Dockerfile to build using code from this repo and test in a CI system 3 | 4 | # Build 5 | First build binaries and copy to this directory 6 | 7 | # docker build --rm --tag heketi/heketi:ci . 8 | 9 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/site.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | become_method: sudo 4 | roles: 5 | - common 6 | 7 | - hosts: gluster 8 | become: yes 9 | become_method: sudo 10 | roles: 11 | - gluster 12 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/site.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | become_method: sudo 4 | roles: 5 | - common 6 | 7 | - hosts: gluster 8 | become: yes 9 | become_method: sudo 10 | roles: 11 | - gluster 12 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/storageclass.yaml.sed: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1beta1 2 | kind: StorageClass 3 | metadata: 4 | name: slow 5 | provisioner: kubernetes.io/glusterfs 6 | parameters: 7 | endpoint: "glusterfs-cluster" 8 | resturl: "%%URL%%" 9 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/site.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | become_method: sudo 4 | roles: 5 | - common 6 | 7 | - hosts: gluster 8 | become: yes 9 | become_method: sudo 10 | roles: 11 | - gluster 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/site.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | become_method: sudo 4 | roles: 5 | - common 6 | 7 | - hosts: gluster 8 | become: yes 9 | become_method: sudo 10 | roles: 11 | - gluster 12 | -------------------------------------------------------------------------------- /extras/docker/gluster/gluster-setup.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Configuring GlusterFS in container 3 | Before=rpcbind.service 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/sbin/gluster-setup.sh 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | 12 | -------------------------------------------------------------------------------- /extras/openshift/service/sample-gluster-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Service", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "glusterfs-cluster" 6 | }, 7 | "spec": { 8 | "ports": [ 9 | {"port": 1} 10 | ] 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/roles/gluster/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install glusterfs 2 | yum: name={{ item }} state=present 3 | with_items: 4 | - glusterfs-server 5 | 6 | - name: start glusterd 7 | service: name=glusterd state=started enabled=yes 8 | -------------------------------------------------------------------------------- /extras/docker/rpi/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Dockerfile and helper build script to build a Heketi container 3 | for Raspberry Pi from an x86_64 machine 4 | 5 | # Build 6 | 7 | ``` 8 | $ ./build-rpi-dockerfile.sh 9 | $ sudo docker push heketi/heketi-rpi 10 | ``` 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | teardown 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/gluster/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install glusterfs 2 | yum: name={{ item }} state=present 3 | with_items: 4 | - glusterfs-server 5 | 6 | - name: start glusterd 7 | service: name=glusterd state=started enabled=yes 8 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | functional_tests 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | functional_tests 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | teardown 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/gluster/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install glusterfs 2 | yum: name={{ item }} state=present 3 | with_items: 4 | - glusterfs-server 5 | 6 | - name: start glusterd 7 | service: name=glusterd state=started enabled=yes 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Heketi code is released under various licenses: 2 | 3 | The REST API client code (in go and python) is released 4 | under a dual license of Apache 2.0 or LGPLv3+. 5 | 6 | The other parts of heketi (server, cli, tests, ...) are released 7 | under a dual license of LGPLv3+ or GPLv2. 8 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/roles/gluster/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install glusterfs 2 | yum: name={{ item }} state=present 3 | with_items: 4 | - glusterfs-server 5 | - glusterfs-client 6 | 7 | - name: start glusterd 8 | service: name=glusterd state=started enabled=yes 9 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | teardown 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | functional_tests 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | functional_tests 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | HEKETI_SERVER_BUILD_DIR=../../.. 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | teardown 11 | 12 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/md5sums: -------------------------------------------------------------------------------- 1 | 8f5de36e01e8f160e91681cbc8355a64 /usr/local/bin/docker-machine 2 | 519d29300df740a73c90d048e5df43fb /usr/local/bin/docker-machine-driver-kvm 3 | eab2ea4df73dd3cbbe147954a161d10f /usr/local/bin/kubectl 4 | b7490e41854ef77bbd89f46ce4bc2edd /usr/local/bin/minikube 5 | -------------------------------------------------------------------------------- /client/api/python/heketi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # flake8: noqa 3 | 4 | __title__ = 'heketi' 5 | __version__ = '1.0.2' 6 | __author__ = 'Heketi authors' 7 | __license__ = 'Apache License (2.0) or LGPLv3+' 8 | __copyright__ = 'Copyright 2016 Heketi authors' 9 | 10 | 11 | from .heketi import HeketiClient 12 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TOP=../../.. 4 | CURRENT_DIR=`pwd` 5 | RESOURCES_DIR=$CURRENT_DIR/resources 6 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 7 | 8 | source ${FUNCTIONAL_DIR}/lib.sh 9 | 10 | 11 | if [ -x /usr/local/bin/minikube ] ; then 12 | minikube stop 13 | minikube delete 14 | fi 15 | rm -rf $RESOURCES_DIR > /dev/null 16 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/mock-endpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Endpoints", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "glusterfs-cluster" 6 | }, 7 | "subsets": [{ 8 | "addresses": [{ 9 | "ip": "192.168.10.32" 10 | }], 11 | "ports": [{ 12 | "port": 1 13 | }] 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /extras/systemd/heketi.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Heketi Server 3 | 4 | [Service] 5 | Type=simple 6 | WorkingDirectory=/var/lib/heketi 7 | EnvironmentFile=-/etc/heketi/heketi.env 8 | User=heketi 9 | ExecStart=/usr/bin/heketi --config=/etc/heketi/heketi.json 10 | Restart=on-failure 11 | StandardOutput=syslog 12 | StandardError=syslog 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /.travis-fork-fix: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Go imports point to github.com/pblcache/pblcache. When 4 | # a fork is tested by Travis-ci, imports may point to code 5 | # from pblcache repo and not from the fork. This program 6 | # will fix the issue. 7 | 8 | REPO="github.com/heketi/heketi" 9 | REPODIR="../../heketi" 10 | 11 | if ! git remote -v | grep origin | grep ${REPO} ; then 12 | mkdir -p ${REPODIR} 13 | ln -s $PWD ${REPODIR}/heketi 14 | fi 15 | -------------------------------------------------------------------------------- /pkg/db/dbvolume.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package db 11 | 12 | const ( 13 | HeketiStorageVolumeName = "heketidbstorage" 14 | ) 15 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/pvc.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind" : "PersistentVolumeClaim", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "claim1", 6 | "annotations": { 7 | "volume.beta.kubernetes.io/storage-class": "slow" 8 | } 9 | }, 10 | "spec": { 11 | "accessModes": [ 12 | "ReadWriteOnce" 13 | ], 14 | "resources": { 15 | "requests": { 16 | "storage": "100Gi" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /extras/docker/gluster/README.md: -------------------------------------------------------------------------------- 1 | # Dockerfile for GlusterFS 2 | Forked from https://github.com/gluster/docker 3 | 4 | # Running 5 | 6 | ```bash 7 | $ sudo docker run -d \ 8 | -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ 9 | -v /run/lvm:/run/lvm \ 10 | -v /dev:/dev \ 11 | -v /var/lib/heketi:/var/lib/heketi \ 12 | --privileged \ 13 | heketi/gluster 14 | ``` 15 | 16 | # Build 17 | 18 | ``` 19 | # docker build --rm --tag heketi/gluster:dev . 20 | ``` 21 | 22 | 23 | -------------------------------------------------------------------------------- /apps/glusterfs/limits.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | var ( 13 | // Default limits 14 | BrickMinSize = uint64(1 * GB) 15 | BrickMaxSize = uint64(4 * TB) 16 | BrickMaxNum = 32 17 | ) 18 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/config/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_glusterfs_comment": "GlusterFS Configuration", 6 | "glusterfs" : { 7 | 8 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 9 | "executor" : "ssh", 10 | 11 | "_db_comment": "Database file name", 12 | "db" : "heketi.db", 13 | 14 | "sshexec" : { 15 | "keyfile" : "config/insecure_private_key", 16 | "user" : "vagrant", 17 | "sudo" : true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /extras/docker/ci/Dockerfile: -------------------------------------------------------------------------------- 1 | # set author and base 2 | FROM centos 3 | MAINTAINER Luis Pabón 4 | 5 | LABEL version="1.0" 6 | LABEL description="CI Development build" 7 | 8 | # post install config and volume setup 9 | ADD ./heketi /usr/bin/heketi 10 | ADD ./heketi-start.sh /usr/bin/heketi-start.sh 11 | ADD ./heketi.json /etc/heketi/heketi.json 12 | 13 | RUN mkdir /var/lib/heketi 14 | VOLUME /var/lib/heketi 15 | 16 | # expose port, set user and set entrypoint with config option 17 | ENTRYPOINT ["/usr/bin/heketi-start.sh"] 18 | EXPOSE 8080 19 | -------------------------------------------------------------------------------- /extras/docker/rpi/Dockerfile: -------------------------------------------------------------------------------- 1 | # set author and base 2 | FROM resin/rpi-raspbian 3 | MAINTAINER Luis Pabón 4 | 5 | LABEL version="1.0.0" 6 | LABEL description="Heketi Container for Raspberry Pi" 7 | 8 | ADD ./heketi /usr/bin/heketi 9 | ADD ./heketi-cli /usr/bin/heketi-cli 10 | ADD ./heketi.json /etc/heketi/heketi.json 11 | ADD ./heketi-start.sh /usr/bin/heketi-start.sh 12 | VOLUME /etc/heketi 13 | 14 | VOLUME /var/lib/heketi 15 | 16 | # expose port, set user and set entrypoint with config option 17 | ENTRYPOINT ["/usr/bin/heketi-start.sh"] 18 | EXPOSE 8080 19 | -------------------------------------------------------------------------------- /extras/docker/rpi/build-rpi-dockerfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is needed to build the executable on an x86 machine for RPi 4 | 5 | fail() { 6 | echo "$1" 7 | exit 1 8 | } 9 | 10 | DOCKERFILEDIR=`pwd` 11 | compile() { 12 | cd ../../.. 13 | env GOOS=linux GOARCH=arm make || fail "Unable to create build" 14 | cp heketi $DOCKERFILEDIR 15 | cp client/cli/go/heketi-cli $DOCKERFILEDIR 16 | make clean 17 | cd $DOCKERFILEDIR 18 | } 19 | 20 | build() { 21 | sudo docker build --rm --tag heketi/heketi-rpi:latest . 22 | } 23 | 24 | compile 25 | build 26 | -------------------------------------------------------------------------------- /client/api/python/unittests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=`pwd` 4 | 5 | # Build server if we need to 6 | if [ ! -x heketi-server ] ; then 7 | ( cd ../../../ ; make ; cp heketi $CURRENT_DIR/heketi-server ) 8 | fi 9 | 10 | # Start server 11 | rm -f heketi.db > /dev/null 2>&1 12 | ./heketi-server --config=test/unit/heketi.json > heketi.log 2>&1 & 13 | pid=$! 14 | sleep 2 15 | 16 | # Start unit tests 17 | tox -e py27 18 | results=$? 19 | 20 | 21 | # kill server 22 | kill $pid 23 | 24 | if [ $results -ne 0 ] ; then 25 | exit $results 26 | fi 27 | 28 | tox -e pep8 29 | results=$? 30 | 31 | exit $results 32 | -------------------------------------------------------------------------------- /apps/app.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package apps 11 | 12 | import ( 13 | "github.com/gorilla/mux" 14 | "net/http" 15 | ) 16 | 17 | type Application interface { 18 | SetRoutes(router *mux.Router) error 19 | Close() 20 | Auth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) 21 | } 22 | -------------------------------------------------------------------------------- /extras/docker/rpi/heketi-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f /backupdb/heketi.db.gz ] ; then 4 | gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db 5 | if [ $? -ne 0 ] ; then 6 | echo "Unable to copy database" 7 | exit 1 8 | fi 9 | echo "Copied backup db to /var/lib/heketi/heketi.db" 10 | elif [ -f /backupdb/heketi.db ] ; then 11 | cp /backupdb/heketi.db /var/lib/heketi/heketi.db 12 | if [ $? -ne 0 ] ; then 13 | echo "Unable to copy database" 14 | exit 1 15 | fi 16 | echo "Copied backup db to /var/lib/heketi/heketi.db" 17 | fi 18 | 19 | /usr/bin/heketi --config=/etc/heketi/heketi.json 20 | -------------------------------------------------------------------------------- /extras/kubernetes/heketi-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f /backupdb/heketi.db.gz ] ; then 4 | gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db 5 | if [ $? -ne 0 ] ; then 6 | echo "Unable to copy database" 7 | exit 1 8 | fi 9 | echo "Copied backup db to /var/lib/heketi/heketi.db" 10 | elif [ -f /backupdb/heketi.db ] ; then 11 | cp /backupdb/heketi.db /var/lib/heketi/heketi.db 12 | if [ $? -ne 0 ] ; then 13 | echo "Unable to copy database" 14 | exit 1 15 | fi 16 | echo "Copied backup db to /var/lib/heketi/heketi.db" 17 | fi 18 | 19 | /usr/bin/heketi --config=/etc/heketi/heketi.json 20 | -------------------------------------------------------------------------------- /extras/docker/fromsource/heketi-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f /backupdb/heketi.db.gz ] ; then 4 | gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db 5 | if [ $? -ne 0 ] ; then 6 | echo "Unable to copy database" 7 | exit 1 8 | fi 9 | echo "Copied backup db to /var/lib/heketi/heketi.db" 10 | elif [ -f /backupdb/heketi.db ] ; then 11 | cp /backupdb/heketi.db /var/lib/heketi/heketi.db 12 | if [ $? -ne 0 ] ; then 13 | echo "Unable to copy database" 14 | exit 1 15 | fi 16 | echo "Copied backup db to /var/lib/heketi/heketi.db" 17 | fi 18 | 19 | /usr/bin/heketi --config=/etc/heketi/heketi.json 20 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: clean disks 2 | command: dd if=/dev/zero of={{ item }} count=100 bs=1024k 3 | with_items: 4 | - /dev/vdb 5 | - /dev/vdc 6 | - /dev/vdd 7 | 8 | - name: install packages 9 | yum: name={{ item }} state=present 10 | with_items: 11 | - epel-release 12 | - centos-release-gluster 13 | 14 | - name: copy private key 15 | copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 16 | 17 | - name: clean iptables 18 | command: iptables -F 19 | 20 | - name: disable selinux 21 | selinux: state=disabled 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | vendor 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[56vq] 13 | [568vq].out 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | 25 | *.vdi 26 | .vagrant 27 | heketi.db 28 | 29 | # Ignore patches and rejects 30 | *.patch 31 | *.rej 32 | *.diff 33 | 34 | 35 | heketi-ci-docker.img 36 | 37 | # Generated files 38 | heketi 39 | client/cli/go/heketi-cli 40 | dist 41 | 42 | # Other 43 | cscope.* 44 | tags 45 | log 46 | *.sw? 47 | *~ 48 | -------------------------------------------------------------------------------- /extras/openshift/endpoint/sample-gluster-endpoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Endpoints", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "glusterfs-cluster" 6 | }, 7 | "subsets": [ 8 | { 9 | "addresses": [ 10 | { 11 | "ip": "10.36.6.105" 12 | } 13 | ], 14 | "ports": [ 15 | { 16 | "port": 1 17 | } 18 | ] 19 | }, 20 | { 21 | "addresses": [ 22 | { 23 | "ip": "10.36.6.105" 24 | } 25 | ], 26 | "ports": [ 27 | { 28 | "port": 1 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: clean disks 2 | command: dd if=/dev/zero of={{ item }} count=100 bs=1024k 3 | with_items: 4 | - /dev/vdb 5 | - /dev/vdc 6 | - /dev/vdd 7 | - /dev/vde 8 | 9 | - name: install packages 10 | yum: name={{ item }} state=present 11 | with_items: 12 | - epel-release 13 | - centos-release-gluster 14 | 15 | - name: copy private key 16 | copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 17 | 18 | - name: clean iptables 19 | command: iptables -F 20 | 21 | - name: disable selinux 22 | selinux: state=disabled 23 | -------------------------------------------------------------------------------- /apps/glusterfs/entry.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/heketi/heketi/pkg/glusterfs/api" 14 | ) 15 | 16 | type Entry struct { 17 | State api.EntryState 18 | } 19 | 20 | func (e *Entry) isOnline() bool { 21 | return e.State == api.EntryStateOnline 22 | } 23 | 24 | func (e *Entry) SetOnline() { 25 | e.State = api.EntryStateOnline 26 | } 27 | -------------------------------------------------------------------------------- /apps/glusterfs/volume_durability.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/heketi/heketi/executors" 14 | ) 15 | 16 | type VolumeDurability interface { 17 | BrickSizeGenerator(size uint64) func() (int, uint64, error) 18 | MinVolumeSize() uint64 19 | BricksInSet() int 20 | SetDurability() 21 | SetExecutorVolumeRequest(v *executors.VolumeRequest) 22 | } 23 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: clean disks 2 | command: dd if=/dev/zero of={{ item }} count=100 bs=1024k 3 | with_items: 4 | - /dev/vdb 5 | - /dev/vdc 6 | - /dev/vdd 7 | - /dev/vde 8 | - /dev/vdf 9 | - /dev/vdg 10 | - /dev/vdh 11 | - /dev/vdi 12 | 13 | - name: install packages 14 | yum: name={{ item }} state=present 15 | with_items: 16 | - epel-release 17 | - centos-release-gluster 18 | 19 | - name: copy private key 20 | copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 21 | 22 | - name: clean iptables 23 | command: iptables -F 24 | 25 | - name: disable selinux 26 | selinux: state=disabled 27 | -------------------------------------------------------------------------------- /executors/kubeexec/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package kubeexec 11 | 12 | import ( 13 | "github.com/heketi/heketi/executors/sshexec" 14 | ) 15 | 16 | type KubeConfig struct { 17 | sshexec.CLICommandConfig 18 | Namespace string `json:"namespace"` 19 | GlusterDaemonSet bool `json:"gluster_daemonset"` 20 | 21 | // Use POD name instead of using label 22 | // to access POD 23 | UsePodNames bool `json:"use_pod_names"` 24 | } 25 | -------------------------------------------------------------------------------- /extras/docker/ci/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : false, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "My Secret" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "My Secret" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "mock", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "/var/lib/heketi/heketi.db" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /extras/docker/rpi/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : false, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "My Secret" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "My Secret" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "mock", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "/var/lib/heketi/heketi.db" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /extras/docker/fromsource/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : false, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "My Secret" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "My Secret" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "mock", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "/var/lib/heketi/heketi.db" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/utils/uuid.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | 9 | package utils 10 | 11 | // From http://www.ashishbanerjee.com/home/go/go-generate-uuid 12 | 13 | import ( 14 | "crypto/rand" 15 | "encoding/hex" 16 | "github.com/lpabon/godbc" 17 | ) 18 | 19 | // Return a 16-byte uuid 20 | func GenUUID() string { 21 | uuid := make([]byte, 16) 22 | n, err := rand.Read(uuid) 23 | godbc.Check(n == len(uuid), n, len(uuid)) 24 | godbc.Check(err == nil, err) 25 | 26 | return hex.EncodeToString(uuid) 27 | } 28 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: clean disks 2 | command: dd if=/dev/zero of={{ item }} count=100 bs=1024k 3 | with_items: 4 | - /dev/vdb 5 | - /dev/vdc 6 | - /dev/vdd 7 | - /dev/vde 8 | - /dev/vdf 9 | - /dev/vdg 10 | - /dev/vdh 11 | - /dev/vdi 12 | - /dev/vdj 13 | - /dev/vdk 14 | 15 | - name: install epel and centos-gluster 16 | yum: name={{ item }} state=present 17 | with_items: 18 | - epel-release 19 | - centos-release-gluster 20 | 21 | - name: copy private key 22 | copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 23 | 24 | - name: clean iptables 25 | command: iptables -F 26 | 27 | - name: disable selinux 28 | selinux: state=disabled 29 | 30 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/heketi/heketi 2 | import: 3 | - package: github.com/auth0/go-jwt-middleware 4 | - package: github.com/boltdb/bolt 5 | version: ^1.3.0 6 | - package: github.com/dgrijalva/jwt-go 7 | version: ^3.0.0 8 | - package: github.com/gorilla/context 9 | - package: github.com/gorilla/mux 10 | - package: github.com/heketi/rest 11 | - package: github.com/heketi/tests 12 | - package: github.com/lpabon/godbc 13 | - package: github.com/spf13/cobra 14 | version: ca57f0f5dba473a8a58765d16d7e811fb8027add 15 | - package: github.com/spf13/pflag 16 | version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 17 | - package: github.com/urfave/negroni 18 | version: ^0.2.0 19 | - package: golang.org/x/crypto 20 | subpackages: 21 | - ssh 22 | - ssh/agent 23 | - package: k8s.io/client-go 24 | version: v3.0.0-beta.0 25 | -------------------------------------------------------------------------------- /apps/glusterfs/testapp_mock.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "bytes" 14 | "github.com/lpabon/godbc" 15 | ) 16 | 17 | func NewTestApp(dbfile string) *App { 18 | 19 | // Create simple configuration for unit tests 20 | appConfig := bytes.NewBuffer([]byte(`{ 21 | "glusterfs" : { 22 | "executor" : "mock", 23 | "allocator" : "simple", 24 | "db" : "` + dbfile + `" 25 | } 26 | }`)) 27 | app := NewApp(appConfig) 28 | godbc.Check(app != nil) 29 | 30 | return app 31 | } 32 | -------------------------------------------------------------------------------- /executors/sshexec/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package sshexec 11 | 12 | type CLICommandConfig struct { 13 | Fstab string `json:"fstab"` 14 | Sudo bool `json:"sudo"` 15 | SnapShotLimit int `json:"snapshot_limit"` 16 | 17 | // Experimental Settings 18 | RebalanceOnExpansion bool `json:"rebalance_on_expansion"` 19 | } 20 | 21 | type SshConfig struct { 22 | CLICommandConfig 23 | PrivateKeyFile string `json:"keyfile"` 24 | User string `json:"user"` 25 | Port string `json:"port"` 26 | } 27 | -------------------------------------------------------------------------------- /pkg/utils/stringstack.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | type StringStack struct { 13 | list []string 14 | } 15 | 16 | func NewStringStack() *StringStack { 17 | a := &StringStack{} 18 | a.list = make([]string, 0) 19 | return a 20 | } 21 | 22 | func (a *StringStack) IsEmpty() bool { 23 | return len(a.list) == 0 24 | } 25 | 26 | func (a *StringStack) Pop() (x string) { 27 | x, a.list = a.list[0], a.list[1:len(a.list)] 28 | return 29 | } 30 | 31 | func (a *StringStack) Push(x string) { 32 | a.list = append(a.list, x) 33 | } 34 | -------------------------------------------------------------------------------- /client/cli/go/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "io" 14 | "os" 15 | 16 | "github.com/heketi/heketi/client/cli/go/cmds" 17 | ) 18 | 19 | var ( 20 | HEKETI_CLI_VERSION = "(dev)" 21 | stdout io.Writer = os.Stdout 22 | stderr io.Writer = os.Stderr 23 | version bool 24 | ) 25 | 26 | func main() { 27 | cmd := cmds.NewHeketiCli(HEKETI_CLI_VERSION, stderr, stdout) 28 | if err := cmd.Execute(); err != nil { 29 | //fmt.Println(err) //Should be used for logging 30 | os.Exit(-1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/README.md: -------------------------------------------------------------------------------- 1 | # Small Functional Test 2 | This functional test can be used on a system with at least 8GB of RAM. 3 | 4 | ## Requirements 5 | 6 | * Vagrant 7 | * Ansible 8 | * Hypervisor: VirtualBox or Libvirt/KVM 9 | 10 | ## Setup 11 | 12 | * Go to `tests/functional/TestSmokeTest/vagrant` 13 | Type: 14 | ``` 15 | $ ./up.sh --provider=PROVIDER 16 | ``` 17 | where PROVIDER is virtualbox or libvirt. 18 | 19 | ## Running the Tests 20 | 21 | * Go to the top of the source tree build and run a new Heketi server: 22 | 23 | ``` 24 | $ rm heketi.db 25 | $ make 26 | $ ./heketi --config=tests/functional/TestSmokeTest/config/heketi.json | tee log 27 | 28 | ``` 29 | 30 | * Once it is ready, then start running the tests in another terminal 31 | 32 | ``` 33 | $ cd tests/functional/TestSmokeTest/tests 34 | $ go test -tags ftsmall 35 | ``` 36 | 37 | Output will be shown by the logs on the heketi server. 38 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/config/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : true, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "adminkey" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "userkey" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "ssh", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "heketi.db", 28 | 29 | "sshexec" : { 30 | "keyfile" : "config/insecure_private_key", 31 | "sudo" : true, 32 | "user" : "vagrant" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/api/python/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,pep8 3 | 4 | [testenv] 5 | install_command = pip install -U {opts} {packages} 6 | setenv = VIRTUAL_ENV={envdir} 7 | NOSE_WITH_COVERAGE=1 8 | NOSE_COVER_BRANCHES=1 9 | NOSE_COVER_PACKAGE=heketi 10 | deps = 11 | -r{toxinidir}/requirements.txt 12 | -r{toxinidir}/test-requirements.txt 13 | changedir = {toxinidir}/test/unit 14 | commands = nosetests -v {posargs} 15 | 16 | [testenv:cover] 17 | setenv = VIRTUAL_ENV={envdir} 18 | NOSE_WITH_COVERAGE=1 19 | NOSE_COVER_BRANCHES=1 20 | NOSE_COVER_HTML=1 21 | NOSE_COVER_HTML_DIR={toxinidir}/cover 22 | 23 | [tox:jenkins] 24 | downloadcache = ~/cache/pip 25 | 26 | [testenv:pep8] 27 | changedir = {toxinidir} 28 | commands = 29 | flake8 heketi test setup.py 30 | 31 | [flake8] 32 | ignore = H 33 | builtins = _ 34 | exclude = .venv,.tox,dist,doc,test,*egg 35 | show-source = True 36 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/config/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : true, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "adminkey" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "userkey" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "ssh", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "heketi.db", 28 | 29 | "sshexec" : { 30 | "keyfile" : "config/insecure_private_key", 31 | "sudo" : true, 32 | "user" : "vagrant" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/README.md: -------------------------------------------------------------------------------- 1 | # TestVolumeSnapshotBehavior 2 | Test that Heketi manages volumes with snapshots correctly 3 | 4 | ## Requirements 5 | 6 | * Vagrant 7 | * Andible 8 | * Hypervisor: Only Libvirt/KVM 9 | 10 | For simplicity you can use the ansible script in https://github.com/heketi/setup-vagrant-libvirt 11 | to setup your system for functional tests. 12 | 13 | ## Setup 14 | 15 | Type: 16 | 17 | ``` 18 | $ ./up 19 | ``` 20 | 21 | ## Running the tests 22 | 23 | * Go to the top of the source tree build and run a new Heketi server: 24 | 25 | ``` 26 | $ rm heketi.db 27 | $ make 28 | $ ./heketi --config=tests/functional/TestVolumeSnapshotBehavior/config/heketi.json 2>&1 | tee log 29 | 30 | ``` 31 | 32 | * Then start running the tests 33 | 34 | ``` 35 | $ cd tests/functional/large/tests 36 | $ go test -timeout=1h -tags ftlarge 37 | ``` 38 | 39 | Output will be shows by the logs on the heketi server. 40 | -------------------------------------------------------------------------------- /pkg/utils/sortedstrings.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "sort" 14 | ) 15 | 16 | // Check if a sorted string list has a string 17 | func SortedStringHas(s sort.StringSlice, x string) bool { 18 | index := s.Search(x) 19 | if index == len(s) { 20 | return false 21 | } 22 | return s[s.Search(x)] == x 23 | } 24 | 25 | // Delete a string from a sorted string list 26 | func SortedStringsDelete(s sort.StringSlice, x string) sort.StringSlice { 27 | index := s.Search(x) 28 | if len(s) != index && s[index] == x { 29 | s = append(s[:index], s[index+1:]...) 30 | } 31 | 32 | return s 33 | } 34 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port" : "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth" : true, 7 | 8 | "_jwt" : "Private keys for access", 9 | "jwt" : { 10 | "_admin" : "Admin has access to all APIs", 11 | "admin" : { 12 | "key" : "adminkey" 13 | }, 14 | "_user" : "User only has access to /volumes endpoint", 15 | "user" : { 16 | "key" : "userkey" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs" : { 22 | 23 | "_executor_comment": "Execute plugin. Possible choices: mock, ssh", 24 | "executor" : "ssh", 25 | 26 | "_db_comment": "Database file name", 27 | "db" : "heketi.db", 28 | 29 | "sshexec" : { 30 | "keyfile" : "config/insecure_private_key", 31 | "sudo" : true, 32 | "user" : "vagrant" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/glusterfs/entry_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "testing" 14 | 15 | "github.com/heketi/heketi/pkg/glusterfs/api" 16 | "github.com/heketi/tests" 17 | ) 18 | 19 | func TestEntryStates(t *testing.T) { 20 | e := &Entry{} 21 | 22 | tests.Assert(t, e.State == api.EntryStateUnknown) 23 | tests.Assert(t, e.isOnline() == false) 24 | 25 | e.State = api.EntryStateOnline 26 | tests.Assert(t, e.isOnline()) 27 | 28 | e.State = api.EntryStateOffline 29 | tests.Assert(t, e.isOnline() == false) 30 | 31 | e.State = api.EntryStateFailed 32 | tests.Assert(t, e.isOnline() == false) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/README.md: -------------------------------------------------------------------------------- 1 | # Large Functional Test 2 | This functional test can be used on a system with at least 128GB of RAM. 3 | 4 | ## Requirements 5 | 6 | * Vagrant 7 | * Andible 8 | * Hypervisor: Only Libvirt/KVM 9 | 10 | For simplicity you can use the ansible script in https://github.com/heketi/setup-vagrant-libvirt 11 | to setup your system for functional tests. 12 | 13 | ## Setup 14 | 15 | Type: 16 | 17 | ``` 18 | $ ./up 19 | ``` 20 | 21 | Note, this could take up to one hour. 22 | 23 | ## Running the tests 24 | 25 | * Go to the top of the source tree build and run a new Heketi server: 26 | 27 | ``` 28 | $ rm heketi.db 29 | $ make 30 | $ ./heketi --config=tests/functional/large/config/heketi.json 2>&1 | tee log 31 | 32 | ``` 33 | 34 | * Then start running the tests 35 | 36 | ``` 37 | $ cd tests/functional/large/tests 38 | $ go test -tags ftlarge 39 | ``` 40 | 41 | Output will be shows by the logs on the heketi server. 42 | -------------------------------------------------------------------------------- /apps/glusterfs/volume_durability_none.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/heketi/heketi/executors" 14 | ) 15 | 16 | type NoneDurability struct { 17 | VolumeReplicaDurability 18 | } 19 | 20 | func NewNoneDurability() *NoneDurability { 21 | n := &NoneDurability{} 22 | n.Replica = 1 23 | 24 | return n 25 | } 26 | 27 | func (n *NoneDurability) SetDurability() { 28 | n.Replica = 1 29 | } 30 | 31 | func (n *NoneDurability) BricksInSet() int { 32 | return 1 33 | } 34 | 35 | func (n *NoneDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { 36 | v.Type = executors.DurabilityNone 37 | v.Replica = n.Replica 38 | } 39 | -------------------------------------------------------------------------------- /pkg/utils/bodystring.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "errors" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | "strings" 18 | ) 19 | 20 | // Return the body from a response as a string 21 | func GetStringFromResponse(r *http.Response) (string, error) { 22 | body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) 23 | if err != nil { 24 | return "", err 25 | } 26 | r.Body.Close() 27 | return string(body), nil 28 | } 29 | 30 | // Return the body from a response as an error 31 | func GetErrorFromResponse(r *http.Response) error { 32 | s, err := GetStringFromResponse(r) 33 | if err != nil { 34 | return err 35 | } 36 | return errors.New(strings.TrimSpace(s)) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/kubernetes/namespace.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package kubernetes 11 | 12 | import ( 13 | "fmt" 14 | "io/ioutil" 15 | "strings" 16 | 17 | "k8s.io/kubernetes/pkg/api" 18 | "k8s.io/kubernetes/pkg/api/v1" 19 | ) 20 | 21 | const ( 22 | KubeServiceAccountDir = "/var/run/secrets/kubernetes.io/serviceaccount/" 23 | KubeNameSpaceFile = KubeServiceAccountDir + v1.ServiceAccountNamespaceKey 24 | ) 25 | 26 | func GetNamespace() (string, error) { 27 | data, err := ioutil.ReadFile(KubeNameSpaceFile) 28 | if err != nil { 29 | return "", fmt.Errorf("File %v not found", KubeNameSpaceFile) 30 | } 31 | if ns := strings.TrimSpace(string(data)); len(ns) > 0 { 32 | return ns, nil 33 | } 34 | return api.NamespaceDefault, nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/utils/stringset.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "sort" 14 | ) 15 | 16 | type StringSet struct { 17 | Set sort.StringSlice 18 | } 19 | 20 | // Create a string set. 21 | // 22 | // A string set is a list where each element appears only once 23 | func NewStringSet() *StringSet { 24 | return &StringSet{ 25 | Set: make(sort.StringSlice, 0), 26 | } 27 | } 28 | 29 | // Add a string to the string set 30 | func (s *StringSet) Add(v string) { 31 | if !SortedStringHas(s.Set, v) { 32 | s.Set = append(s.Set, v) 33 | s.Set.Sort() 34 | } 35 | } 36 | 37 | // Return string list 38 | func (s *StringSet) Strings() []string { 39 | return s.Set 40 | } 41 | 42 | func (s *StringSet) Len() int { 43 | return len(s.Set) 44 | } 45 | -------------------------------------------------------------------------------- /extras/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Kubernetes templates for Heketi and Gluster. The following documentation is setup 3 | to deploy the containers in Kubernetes. It is not a full setup. For full 4 | documentation, please visit the Heketi wiki page. 5 | 6 | # Usage 7 | 8 | ## Deploy Gluster 9 | 10 | * Get node name by running: 11 | 12 | ``` 13 | $ kubectl get nodes 14 | ``` 15 | 16 | * Deploy the GlusterFS DaemonSet 17 | 18 | ``` 19 | $ kubectl create -f gluster-daemonset.json 20 | ``` 21 | 22 | * Deploy gluster container onto specified node by setting the label 23 | `storagenode=glusterfs` on that node: 24 | 25 | ``` 26 | $ kubectl label node <...node...> storagenode=glusterfs 27 | ``` 28 | 29 | Repeat as needed. 30 | 31 | ## Deploy Heketi 32 | 33 | First you will need to deploy the bootstrap Heketi container: 34 | 35 | ``` 36 | $ kubectl create -f deploy-heketi-deployment.json 37 | ``` 38 | 39 | This will deploy the a Heketi container used to bootstrap the Heketi 40 | database. Please refer to the wiki Kubernetes Deployment page for 41 | more information 42 | 43 | -------------------------------------------------------------------------------- /apps/glusterfs/errors.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "errors" 14 | ) 15 | 16 | var ( 17 | ErrNoSpace = errors.New("No space") 18 | ErrNotFound = errors.New("Id not found") 19 | ErrConflict = errors.New("The target exists, contains other items, or is in use.") 20 | ErrMaxBricks = errors.New("Maximum number of bricks reached.") 21 | ErrMinimumBrickSize = errors.New("Minimum brick size limit reached. Out of space.") 22 | ErrDbAccess = errors.New("Unable to access db") 23 | ErrAccessList = errors.New("Unable to access list") 24 | ErrKeyExists = errors.New("Key already exists in the database") 25 | ErrNoReplacement = errors.New("No Replacement was found for resource requested to be removed") 26 | ) 27 | -------------------------------------------------------------------------------- /pkg/utils/jsonutils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "encoding/json" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | ) 18 | 19 | func jsonFromBody(r io.Reader, v interface{}) error { 20 | 21 | // Check body 22 | body, err := ioutil.ReadAll(r) 23 | if err != nil { 24 | return err 25 | } 26 | if err := json.Unmarshal(body, v); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | // Unmarshal JSON from request 34 | func GetJsonFromRequest(r *http.Request, v interface{}) error { 35 | defer r.Body.Close() 36 | return jsonFromBody(r.Body, v) 37 | } 38 | 39 | // Unmarshal JSON from response 40 | func GetJsonFromResponse(r *http.Response, v interface{}) error { 41 | defer r.Body.Close() 42 | return jsonFromBody(r.Body, v) 43 | } 44 | -------------------------------------------------------------------------------- /apps/glusterfs/allocator.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | type Allocator interface { 13 | 14 | // Inform the brick allocator to include device 15 | AddDevice(c *ClusterEntry, n *NodeEntry, d *DeviceEntry) error 16 | 17 | // Inform the brick allocator to not use the specified device 18 | RemoveDevice(c *ClusterEntry, n *NodeEntry, d *DeviceEntry) error 19 | 20 | // Remove cluster information from allocator 21 | RemoveCluster(clusterId string) error 22 | 23 | // Returns a generator, done, and error channel. 24 | // The generator returns the location for the brick, then the possible locations 25 | // of its replicas. The caller must close() the done channel when it no longer 26 | // needs to read from the generator. 27 | GetNodes(clusterId, brickId string) (<-chan string, 28 | chan<- struct{}, <-chan error) 29 | } 30 | -------------------------------------------------------------------------------- /client/api/go-client/backup.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), as published by the Free Software Foundation, 7 | // or under the Apache License, Version 2.0 . 9 | // 10 | // You may not use this file except in compliance with those terms. 11 | // 12 | 13 | package client 14 | 15 | import ( 16 | "io" 17 | "net/http" 18 | 19 | "github.com/heketi/heketi/pkg/utils" 20 | ) 21 | 22 | func (c *Client) BackupDb(w io.Writer) error { 23 | // Create a request 24 | req, err := http.NewRequest("GET", c.host+"/backup/db", nil) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | // Set token 30 | err = c.setToken(req) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | // Send request 36 | r, err := c.do(req) 37 | if err != nil { 38 | return err 39 | } 40 | if r.StatusCode != http.StatusOK { 41 | return utils.GetErrorFromResponse(r) 42 | } 43 | 44 | // Read data from response 45 | defer r.Body.Close() 46 | _, err = io.Copy(w, r.Body) 47 | 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /client/api/python/test/unit/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port": "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth": true, 7 | 8 | "_jwt": "Private keys for access", 9 | "jwt": { 10 | "_admin": "Admin has access to all APIs", 11 | "admin": { 12 | "key": "My Secret" 13 | }, 14 | "_user": "User only has access to /volumes endpoint", 15 | "user": { 16 | "key": "My Secret" 17 | } 18 | }, 19 | 20 | "_glusterfs_comment": "GlusterFS Configuration", 21 | "glusterfs": { 22 | "_executor_comment": [ 23 | "Execute plugin. Possible choices: mock, ssh", 24 | "mock: This setting is used for testing and development.", 25 | " It will not send commands to any node.", 26 | "ssh: This setting will notify Heketi to ssh to the nodes.", 27 | " It will need the values in sshexec to be configured." 28 | ], 29 | "executor": "mock", 30 | 31 | "_sshexec_comment": "SSH username and private key file information", 32 | "sshexec": { 33 | "keyfile": "path/to/private_key", 34 | "user": "sshuser" 35 | }, 36 | 37 | "_db_comment": "Database file name", 38 | "db": "heketi.db" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/functional/README.md: -------------------------------------------------------------------------------- 1 | # Automated Functional Test 2 | 3 | ## Requirements 4 | You will need a system with at least 16G of ram. If you only have 8G, you can at least run the TestSmokeTest. 5 | 6 | ### Packages 7 | 8 | ``` 9 | # dnf -y install docker libvirt qemu-kvm \ 10 | ansible vagrant vagrant-libvirt go git make 11 | ``` 12 | 13 | Make sure docker and libvirt deamons are running 14 | 15 | ### User 16 | 17 | The user running the tests must have password-less sudo access 18 | 19 | ## Setup 20 | 21 | ``` 22 | $ mkdir go 23 | $ cd go 24 | $ export GOPATH=$PWD 25 | $ export PATH=$PATH:$GOPATH/bin 26 | $ curl https://glide.sh/get | sh 27 | $ mkdir -p src/github.com/heketi 28 | $ cd src/github.com/heketi 29 | $ git clone https://github.com/heketi/heketi.git 30 | ``` 31 | 32 | ## Running 33 | 34 | Run the entire suite: 35 | 36 | ``` 37 | $ cd $GOPATH/src/github.com/heketi/heketi/tests/functional 38 | $ ./run.sh 39 | ``` 40 | 41 | To run a specific functional test, go into that functionl test's directory and type: 42 | 43 | ``` 44 | $ ./run.sh 45 | ``` 46 | 47 | ## Adding new tests 48 | 49 | Create a new directory under tests/functional matching the style of 50 | the current ones. Create a shell script called `run.sh` in that directory 51 | which will run that test. 52 | -------------------------------------------------------------------------------- /.travis-coverage: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # We cannot use go test -coverprofile=cover.out ./... because 4 | # the tool requires that it be used only on one package when 5 | # capturing the coverage 6 | # This is why we need this little script here. 7 | packages="./apps/glusterfs" 8 | packages="${packages} ./executors/sshexec" 9 | packages="${packages} ./executors/mockexec" 10 | packages="${packages} ./executors/kubeexec" 11 | packages="${packages} ./client/api/go-client" 12 | packages="${packages} ./middleware" 13 | packages="${packages} ./pkg/utils" 14 | COVERFILE=packagecover.out 15 | 16 | coverage() 17 | { 18 | 19 | echo "mode: count" > $COVERFILE 20 | for pkg in $packages ; do 21 | echo "-- Testing $pkg --" 22 | 23 | # Collect coverage 24 | go test -covermode=count -coverprofile=cover.out $pkg || exit 1 25 | 26 | # Show in the command line 27 | go tool cover -func=cover.out 28 | 29 | # Append to coverfile 30 | grep -v "^mode: count" cover.out >> $COVERFILE 31 | 32 | # Cleanup 33 | rm -f cover.out 34 | done 35 | } 36 | 37 | coverage 38 | 39 | if [ -n "$COVERALLS_TOKEN" ] ; then 40 | # Send to coveralls.io 41 | $HOME/gopath/bin/goveralls \ 42 | -coverprofile=$COVERFILE \ 43 | -service=travis-ci \ 44 | -repotoken $COVERALLS_TOKEN 45 | fi 46 | 47 | # Clean up 48 | rm -f $COVERFILE > /dev/null 2>&1 49 | exit 0 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: true 3 | before_install: 4 | - sudo apt-get update -qq 5 | - sudo apt-get install -qq python python-pip 6 | - sudo pip install tox nose 7 | install: 8 | - bash .travis-fork-fix 9 | - go get github.com/mattn/goveralls 10 | - go get github.com/wadey/gocovmerge 11 | - curl https://glide.sh/get | sh 12 | env: 13 | global: 14 | - secure: tCj+iGIN2GM5yPneme35KIQwqGcXMOLod00qvG/Af0lkjEVJRRNz3gnB3P2dNyj9Nc4FWxSUIjCiIkblOMaEKxPXp1S3Zo7gRBVphyNY5ZvIKeqKoXvBPd6hi9Ft2TaN+4vDczfAKOI/S3/3kN3NmgGCYNTLOues0T4yhVd3v14hoQJxw4Jbjlsj8RGfLrqp+dInFv2tS+xTyK+q/EOiaCpBq4PfK6giKwt943o7jc9v0iWjnP2rWq/AotMo4QutoC0OVeJT8aG41sC5LvlYTBQB22E8Zv439JgHsdhQU1NRd/1VLGKATToxkUxh2Reei42koAWFJ+EfFvAIx03k5+ZYJY7W+Rtuy8jn0uRaZyvvQdUvyT22e9lSJzqkP6JAe7oru9hf9X4K0XSOfMMFUiJDC+rNm0Ajd+r/5h6C+jRqIMDvvFgdlCkM8gKIX1B5N+RM1hxurAGTRpdCPuDVLVeTCbNZCds8jiK1DNky6Ni66plBIV+LKQY3EpjBn0jaWfPdTJbU5OiOb1uadnmzj2yt65Mp3T8QJD3dotURISR8bIS+Xb6vAytKFWmtcqje5Hx4lFTfyrH3gRGjMyeS9j3pVjbbCCV466FHOp9oglpoFv49nXhivPzLqU7mSuLIue+5RZ318HykuBWI+6xAo8aH9nnoBmAWiGCxXwIr13Y= 15 | matrix: 16 | include: 17 | - go: 1.7.5 18 | env: OPTIONS="-race" 19 | - go: 1.8.2 20 | env: COVERAGE="true" 21 | script: 22 | - go fmt $(go list ./... | grep -v vendor) | wc -l | grep 0 23 | - go vet $(go list ./... | grep -v vendor) 24 | - make 25 | - if [[ -z "$COVERAGE" ]]; then make test ; fi 26 | - if [[ -n "$COVERAGE" ]]; then bash .travis-coverage; fi 27 | - cd client/api/python 28 | - ./unittests.sh 29 | - tox -e pep8 30 | -------------------------------------------------------------------------------- /extras/docker/fromsource/Dockerfile: -------------------------------------------------------------------------------- 1 | # set author and base 2 | FROM fedora 3 | MAINTAINER Luis Pabón 4 | 5 | LABEL version="1.3.1" 6 | LABEL description="Development build" 7 | 8 | # let's setup all the necessary environment variables 9 | ENV BUILD_HOME=/build 10 | ENV GOPATH=$BUILD_HOME/golang 11 | ENV PATH=$GOPATH/bin:$PATH 12 | ENV HEKETI_BRANCH="master" 13 | 14 | RUN curl https://glide.sh/get | sh 15 | 16 | # install dependencies, build and cleanup 17 | RUN mkdir $BUILD_HOME $GOPATH && \ 18 | dnf -q -y install golang git && \ 19 | dnf -q -y install make && \ 20 | dnf -q -y clean all && \ 21 | mkdir -p $GOPATH/src/github.com/heketi && \ 22 | cd $GOPATH/src/github.com/heketi && \ 23 | git clone -b $HEKETI_BRANCH https://github.com/heketi/heketi.git && \ 24 | cd $GOPATH/src/github.com/heketi/heketi && \ 25 | glide install -v && make && \ 26 | cp heketi /usr/bin/heketi && \ 27 | cp client/cli/go/heketi-cli /usr/bin/heketi-cli && \ 28 | cd && rm -rf $BUILD_HOME && \ 29 | dnf -q -y remove git golang make && \ 30 | dnf -q -y autoremove && \ 31 | dnf -q -y clean all 32 | 33 | # post install config and volume setup 34 | ADD ./heketi.json /etc/heketi/heketi.json 35 | ADD ./heketi-start.sh /usr/bin/heketi-start.sh 36 | VOLUME /etc/heketi 37 | 38 | RUN mkdir /var/lib/heketi 39 | VOLUME /var/lib/heketi 40 | 41 | # expose port, set user and set entrypoint with config option 42 | ENTRYPOINT ["/usr/bin/heketi-start.sh"] 43 | EXPOSE 8080 44 | -------------------------------------------------------------------------------- /tests/functional/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source ./lib.sh 4 | 5 | teardown_all() { 6 | results=0 7 | for testDir in * ; do 8 | if [ -x $testDir/teardown.sh ] ; then 9 | println "TEARDOWN $testDir" 10 | cd $testDir 11 | teardown.sh 12 | cd .. 13 | fi 14 | done 15 | } 16 | 17 | ### MAIN ### 18 | 19 | # See https://bugzilla.redhat.com/show_bug.cgi?id=1327740 20 | _sudo setenforce 0 21 | 22 | starttime=`date` 23 | export PATH=$PATH:. 24 | 25 | # Check go can build 26 | if [ -z $GOPATH ] ; then 27 | fail "GOPATH must be specified" 28 | fi 29 | 30 | # Clean up 31 | rm -f heketi-server > /dev/null 2>&1 32 | teardown_all 33 | 34 | # Check each dir for tests 35 | results=0 36 | for testDir in * ; do 37 | if [ -x $testDir/run.sh ] ; then 38 | println "TEST $testDir" 39 | cd $testDir 40 | 41 | # Run the command with a large timeout. 42 | # Just large enough so that it doesn't run forever. 43 | timeout 1h run.sh ; result=$? 44 | 45 | if [ $result -ne 0 ] ; then 46 | println "FAILED $testDir" 47 | println "TEARDOWN $testDir" 48 | teardown.sh 49 | results=1 50 | else 51 | println "PASSED $testDir" 52 | fi 53 | 54 | cd .. 55 | fi 56 | done 57 | 58 | # Summary 59 | println "Started $starttime" 60 | println "Ended `date`" 61 | if [ $results -eq 0 ] ; then 62 | println "PASSED" 63 | else 64 | println "FAILED" 65 | fi 66 | 67 | exit $results 68 | -------------------------------------------------------------------------------- /client/api/python/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 heketi authors 2 | # 3 | # This file is licensed to you under your choice of the GNU Lesser 4 | # General Public License, version 3 or any later version (LGPLv3 or 5 | # later), as published by the Free Software Foundation, 6 | # or under the Apache License, Version 2.0 . 8 | # 9 | # You may not use this file except in compliance with those terms. 10 | 11 | from setuptools import setup, find_packages 12 | 13 | setup( 14 | name='heketi', 15 | version='3.0.0', 16 | description='Python client library for Heketi', 17 | license='Apache License (2.0) or LGPLv3+', 18 | author='Luis Pabon', 19 | author_email='lpabon@redhat.com', 20 | url='https://github.com/heketi/heketi/tree/master/client/api/python', 21 | packages=find_packages(exclude=['test', 'bin']), 22 | test_suite='nose.collector', 23 | install_requires=['pyjwt', 'requests'], 24 | classifiers=[ 25 | 'Development Status :: 5 - Production/Stable', 26 | 'Intended Audience :: Information Technology', 27 | 'Intended Audience :: System Administrators', 28 | 'License :: OSI Approved :: Apache Software License', 29 | 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', # noqa 30 | 'Operating System :: POSIX :: Linux', 31 | 'Programming Language :: Python', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Topic :: System :: Filesystems', 34 | 'Topic :: System :: Distributed Computing', 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /pkg/utils/statusgroup_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "github.com/heketi/tests" 16 | "testing" 17 | "time" 18 | ) 19 | 20 | func TestNewStatusGroup(t *testing.T) { 21 | s := NewStatusGroup() 22 | tests.Assert(t, s != nil) 23 | tests.Assert(t, s.results != nil) 24 | tests.Assert(t, len(s.results) == 0) 25 | tests.Assert(t, s.err == nil) 26 | } 27 | 28 | func TestStatusGroupSuccess(t *testing.T) { 29 | 30 | s := NewStatusGroup() 31 | 32 | max := 100 33 | s.Add(max) 34 | 35 | for i := 0; i < max; i++ { 36 | go func(value int) { 37 | defer s.Done() 38 | time.Sleep(time.Millisecond * 1 * time.Duration(value)) 39 | }(i) 40 | } 41 | 42 | err := s.Result() 43 | tests.Assert(t, err == nil) 44 | 45 | } 46 | 47 | func TestStatusGroupFailure(t *testing.T) { 48 | s := NewStatusGroup() 49 | 50 | for i := 0; i < 100; i++ { 51 | 52 | s.Add(1) 53 | go func(value int) { 54 | defer s.Done() 55 | time.Sleep(time.Millisecond * 1 * time.Duration(value)) 56 | if value%10 == 0 { 57 | s.Err(errors.New(fmt.Sprintf("Err: %v", value))) 58 | } 59 | 60 | }(i) 61 | 62 | } 63 | 64 | err := s.Result() 65 | 66 | tests.Assert(t, err != nil) 67 | tests.Assert(t, err.Error() == "Err: 90", err) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /tests/functional/lib.sh: -------------------------------------------------------------------------------- 1 | 2 | fail() { 3 | echo "==> ERROR: $@" 4 | exit 1 5 | } 6 | 7 | println() { 8 | echo "==> $1" 9 | } 10 | 11 | _sudo() { 12 | if [ ${UID} = 0 ] ; then 13 | ${@} 14 | else 15 | sudo -E ${@} 16 | fi 17 | } 18 | 19 | HEKETI_PID= 20 | start_heketi() { 21 | ( cd $HEKETI_SERVER_BUILD_DIR && make && cp heketi $HEKETI_SERVER ) 22 | if [ $? -ne 0 ] ; then 23 | fail "Unable to build Heketi" 24 | fi 25 | 26 | # Start server 27 | rm -f heketi.db > /dev/null 2>&1 28 | $HEKETI_SERVER --config=config/heketi.json & 29 | HEKETI_PID=$! 30 | sleep 2 31 | } 32 | 33 | start_vagrant() { 34 | cd vagrant 35 | _sudo ./up.sh || fail "unable to start vagrant virtual machines" 36 | cd .. 37 | } 38 | 39 | teardown_vagrant() { 40 | cd vagrant 41 | _sudo vagrant destroy -f 42 | cd .. 43 | } 44 | 45 | run_tests() { 46 | cd tests 47 | go test -timeout=1h -tags functional 48 | gotest_result=$? 49 | cd .. 50 | } 51 | 52 | force_cleanup_libvirt_disks() { 53 | # Sometimes disks are not deleted 54 | for i in `_sudo virsh vol-list default | grep "*.disk" | awk '{print $1}'` ; do 55 | _sudo virsh vol-delete --pool default "${i}" || fail "Unable to delete disk $i" 56 | done 57 | } 58 | 59 | teardown() { 60 | teardown_vagrant 61 | force_cleanup_libvirt_disks 62 | rm -f heketi.db > /dev/null 2>&1 63 | } 64 | 65 | functional_tests() { 66 | start_vagrant 67 | start_heketi 68 | 69 | run_tests 70 | 71 | kill $HEKETI_PID 72 | teardown 73 | 74 | exit $gotest_result 75 | } 76 | 77 | -------------------------------------------------------------------------------- /pkg/kubernetes/pv.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package kubernetes 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/heketi/heketi/pkg/glusterfs/api" 16 | 17 | "k8s.io/apimachinery/pkg/api/resource" 18 | kubeapi "k8s.io/kubernetes/pkg/api/v1" 19 | ) 20 | 21 | func VolumeToPv(volume *api.VolumeInfoResponse, 22 | name, endpoint string) *kubeapi.PersistentVolume { 23 | // Initialize object 24 | pv := &kubeapi.PersistentVolume{} 25 | pv.Kind = "PersistentVolume" 26 | pv.APIVersion = "v1" 27 | pv.Spec.PersistentVolumeReclaimPolicy = kubeapi.PersistentVolumeReclaimRetain 28 | pv.Spec.AccessModes = []kubeapi.PersistentVolumeAccessMode{ 29 | kubeapi.ReadWriteMany, 30 | } 31 | pv.Spec.Capacity = make(kubeapi.ResourceList) 32 | pv.Spec.Glusterfs = &kubeapi.GlusterfsVolumeSource{} 33 | 34 | // Set path 35 | pv.Spec.Capacity[kubeapi.ResourceStorage] = 36 | resource.MustParse(fmt.Sprintf("%vGi", volume.Size)) 37 | pv.Spec.Glusterfs.Path = volume.Name 38 | 39 | // Set name 40 | if name == "" { 41 | pv.ObjectMeta.Name = "glusterfs-" + volume.Id[:8] 42 | } else { 43 | pv.ObjectMeta.Name = name 44 | 45 | } 46 | 47 | // Set endpoint 48 | if endpoint == "" { 49 | pv.Spec.Glusterfs.EndpointsName = "TYPE ENDPOINT HERE" 50 | } else { 51 | pv.Spec.Glusterfs.EndpointsName = endpoint 52 | } 53 | 54 | return pv 55 | } 56 | -------------------------------------------------------------------------------- /pkg/utils/stringset_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "github.com/heketi/tests" 14 | "testing" 15 | ) 16 | 17 | func TestNewStringSet(t *testing.T) { 18 | s := NewStringSet() 19 | tests.Assert(t, s.Set != nil) 20 | tests.Assert(t, len(s.Set) == 0) 21 | } 22 | 23 | func TestStringSet(t *testing.T) { 24 | s := NewStringSet() 25 | 26 | s.Add("one") 27 | s.Add("two") 28 | s.Add("three") 29 | tests.Assert(t, s.Len() == 3) 30 | tests.Assert(t, SortedStringHas(s.Set, "one")) 31 | tests.Assert(t, SortedStringHas(s.Set, "two")) 32 | tests.Assert(t, SortedStringHas(s.Set, "three")) 33 | 34 | s.Add("one") 35 | tests.Assert(t, s.Len() == 3) 36 | tests.Assert(t, SortedStringHas(s.Set, "one")) 37 | tests.Assert(t, SortedStringHas(s.Set, "two")) 38 | tests.Assert(t, SortedStringHas(s.Set, "three")) 39 | 40 | s.Add("three") 41 | tests.Assert(t, s.Len() == 3) 42 | tests.Assert(t, SortedStringHas(s.Set, "one")) 43 | tests.Assert(t, SortedStringHas(s.Set, "two")) 44 | tests.Assert(t, SortedStringHas(s.Set, "three")) 45 | 46 | s.Add("four") 47 | tests.Assert(t, s.Len() == 4) 48 | tests.Assert(t, SortedStringHas(s.Set, "one")) 49 | tests.Assert(t, SortedStringHas(s.Set, "two")) 50 | tests.Assert(t, SortedStringHas(s.Set, "three")) 51 | tests.Assert(t, SortedStringHas(s.Set, "four")) 52 | } 53 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | 5 | NODES = 3 6 | DISKS = 10 7 | DRIVE_SIZE = "100G" 8 | 9 | Vagrant.configure("2") do |config| 10 | config.ssh.insert_key = false 11 | config.vm.box = "centos/7" 12 | config.vm.synced_folder '.', '/home/vagrant/sync', disabled: true 13 | 14 | # Make the glusterfs cluster, each with DISKS number of drives 15 | (0..NODES-1).each do |i| 16 | config.vm.define "storage#{i}" do |storage| 17 | storage.vm.hostname = "storage#{i}" 18 | localip = 100+i 19 | storage.vm.network :private_network, ip: "192.168.10.#{localip}" 20 | (0..DISKS-1).each do |d| 21 | driverletters = ('b'..'z').to_a 22 | storage.vm.provider :libvirt do |lv| 23 | lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_many_bricks_disk-#{i}-#{d}.disk", :size => "#{DRIVE_SIZE}" 24 | lv.memory = 2048 25 | lv.cpus =2 26 | end 27 | end 28 | 29 | if i == (NODES-1) 30 | # View the documentation for the provider you're using for more 31 | # information on available options. 32 | storage.vm.provision :ansible do |ansible| 33 | ansible.limit = "all" 34 | ansible.playbook = "site.yml" 35 | ansible.groups = { 36 | "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, 37 | } 38 | end 39 | end 40 | end 41 | end 42 | end 43 | 44 | -------------------------------------------------------------------------------- /apps/glusterfs/app_config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "encoding/json" 14 | "io" 15 | "os" 16 | 17 | "github.com/heketi/heketi/executors/kubeexec" 18 | "github.com/heketi/heketi/executors/sshexec" 19 | ) 20 | 21 | type GlusterFSConfig struct { 22 | DBfile string `json:"db"` 23 | Executor string `json:"executor"` 24 | Allocator string `json:"allocator"` 25 | SshConfig sshexec.SshConfig `json:"sshexec"` 26 | KubeConfig kubeexec.KubeConfig `json:"kubeexec"` 27 | Loglevel string `json:"loglevel"` 28 | 29 | // advanced settings 30 | BrickMaxSize int `json:"brick_max_size_gb"` 31 | BrickMinSize int `json:"brick_min_size_gb"` 32 | BrickMaxNum int `json:"max_bricks_per_volume"` 33 | } 34 | 35 | type ConfigFile struct { 36 | GlusterFS GlusterFSConfig `json:"glusterfs"` 37 | } 38 | 39 | func loadConfiguration(configIo io.Reader) *GlusterFSConfig { 40 | configParser := json.NewDecoder(configIo) 41 | 42 | var config ConfigFile 43 | if err := configParser.Decode(&config); err != nil { 44 | logger.LogError("Unable to parse config file: %v\n", 45 | err.Error()) 46 | return nil 47 | } 48 | 49 | // Set environment variable to override configuration file 50 | env := os.Getenv("HEKETI_EXECUTOR") 51 | if env != "" { 52 | config.GlusterFS.Executor = env 53 | } 54 | 55 | return &config.GlusterFS 56 | } 57 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | 5 | NODES = 4 6 | DISKS = 8 7 | 8 | Vagrant.configure("2") do |config| 9 | config.ssh.insert_key = false 10 | 11 | config.vm.provider :libvirt do |v,override| 12 | override.vm.box = "centos/7" 13 | override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true 14 | end 15 | 16 | # Make the glusterfs cluster, each with DISKS number of drives 17 | (0..NODES-1).each do |i| 18 | config.vm.define "storage#{i}" do |storage| 19 | storage.vm.hostname = "storage#{i}" 20 | storage.vm.network :private_network, ip: "192.168.10.10#{i}" 21 | driverletters = ('b'..'z').to_a 22 | (0..DISKS-1).each do |d| 23 | storage.vm.provider :libvirt do |lv| 24 | lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_volume_snapshot_disk-#{i}-#{d}.disk", :size => '500G' 25 | lv.memory = 1024 26 | lv.cpus =2 27 | end 28 | end 29 | 30 | if i == (NODES-1) 31 | # View the documentation for the provider you're using for more 32 | # information on available options. 33 | storage.vm.provision :ansible do |ansible| 34 | ansible.limit = "all" 35 | ansible.playbook = "site.yml" 36 | ansible.groups = { 37 | "heketi" => ["storage0"], 38 | "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, 39 | } 40 | 41 | end 42 | end 43 | end 44 | end 45 | end 46 | 47 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | 5 | NODES = 3 6 | DISKS = 3 7 | 8 | Vagrant.configure("2") do |config| 9 | config.ssh.insert_key = false 10 | 11 | config.vm.provider :libvirt do |v,override| 12 | override.vm.box = "centos/7" 13 | override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true 14 | end 15 | 16 | # Make the glusterfs cluster, each with DISKS number of drives 17 | (0..NODES-1).each do |i| 18 | config.vm.define "storage#{i}" do |storage| 19 | storage.vm.hostname = "storage#{i}" 20 | storage.vm.network :private_network, ip: "192.168.10.10#{i}" 21 | driverletters = ('b'..'z').to_a 22 | (0..DISKS-1).each do |d| 23 | storage.vm.provider :libvirt do |lv| 24 | lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_volume_delete_disk-#{i}-#{d}.disk", :size => '500G' 25 | lv.memory = 1024 26 | lv.cpus =2 27 | end 28 | end 29 | 30 | if i == (NODES-1) 31 | # View the documentation for the provider you're using for more 32 | # information on available options. 33 | storage.vm.provision :ansible do |ansible| 34 | ansible.limit = "all" 35 | ansible.playbook = "site.yml" 36 | ansible.groups = { 37 | "heketi" => ["storage0"], 38 | "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, 39 | } 40 | 41 | end 42 | end 43 | end 44 | end 45 | end 46 | 47 | -------------------------------------------------------------------------------- /pkg/utils/statusgroup.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "sync" 14 | ) 15 | 16 | type StatusGroup struct { 17 | wg sync.WaitGroup 18 | results chan error 19 | err error 20 | } 21 | 22 | // Create a new goroutine error status collector 23 | func NewStatusGroup() *StatusGroup { 24 | s := &StatusGroup{} 25 | s.results = make(chan error, 1) 26 | 27 | return s 28 | } 29 | 30 | // Adds to the number of goroutines it should wait 31 | func (s *StatusGroup) Add(delta int) { 32 | s.wg.Add(delta) 33 | } 34 | 35 | // Removes the number of pending goroutines by one 36 | func (s *StatusGroup) Done() { 37 | s.wg.Done() 38 | } 39 | 40 | // Goroutine can return an error back to caller 41 | func (s *StatusGroup) Err(err error) { 42 | s.results <- err 43 | } 44 | 45 | // Returns an error if any of the spawned goroutines 46 | // return an error. Only the last error is saved. 47 | // This function must be called last after the last 48 | // s.Register() function 49 | func (s *StatusGroup) Result() error { 50 | 51 | // This goroutine will wait until all 52 | // other privously spawned goroutines finish. 53 | // Once they finish, it will close the channel 54 | go func() { 55 | s.wg.Wait() 56 | close(s.results) 57 | }() 58 | 59 | // Read from the channel until close 60 | for err := range s.results { 61 | // Only save the last one 62 | if err != nil { 63 | s.err = err 64 | } 65 | } 66 | 67 | return s.err 68 | } 69 | -------------------------------------------------------------------------------- /client/cli/go/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Based on http://chrismckenzie.io/post/deploying-with-golang/ 3 | # 4 | 5 | .PHONY: version all run dist clean 6 | 7 | APP_NAME := heketi-cli 8 | SHA := $(shell git rev-parse --short HEAD) 9 | BRANCH := $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 10 | VER := $(shell git describe) 11 | ARCH := $(shell go env GOARCH) 12 | GOOS := $(shell go env GOOS) 13 | DIR=. 14 | 15 | ifdef APP_SUFFIX 16 | VERSION = $(VER)-$(subst /,-,$(APP_SUFFIX)) 17 | else 18 | ifeq (master,$(BRANCH)) 19 | VERSION = $(VER) 20 | else 21 | VERSION = $(VER)-$(BRANCH) 22 | endif 23 | endif 24 | 25 | # Go setup 26 | GO=go 27 | TEST="go test" 28 | 29 | # Sources and Targets 30 | EXECUTABLES :=$(APP_NAME) 31 | # Build Binaries setting main.version and main.build vars 32 | LDFLAGS :=-ldflags "-X main.HEKETI_CLI_VERSION=$(VERSION) -extldflags '-z relro -z now'" 33 | # Package target 34 | PACKAGE :=$(DIR)/dist/$(APP_NAME)-$(VERSION).$(GOOS).$(ARCH).tar.gz 35 | 36 | .DEFAULT: all 37 | 38 | all: build 39 | 40 | # print the version 41 | version: 42 | @echo $(VERSION) 43 | 44 | # print the name of the app 45 | name: 46 | @echo $(APP_NAME) 47 | 48 | # print the package path 49 | package: 50 | @echo $(PACKAGE) 51 | 52 | build: 53 | go build $(LDFLAGS) -o $(APP_NAME) 54 | 55 | run: build 56 | ./$(APP_NAME) 57 | 58 | test: 59 | go test ./... 60 | 61 | clean: 62 | @echo Cleaning Workspace... 63 | rm -rf $(APP_NAME) 64 | rm -rf dist 65 | 66 | $(PACKAGE): all 67 | @echo Packaging Binaries... 68 | @mkdir -p tmp/$(APP_NAME) 69 | @cp $(APP_NAME) tmp/$(APP_NAME)/ 70 | @cp etc/heketi.json tmp/$(APP_NAME)/ 71 | @mkdir -p $(DIR)/dist/ 72 | tar -czf $@ -C tmp $(APP_NAME); 73 | @rm -rf tmp 74 | @echo 75 | @echo Package $@ saved in dist directory 76 | 77 | dist: $(PACKAGE) 78 | -------------------------------------------------------------------------------- /extras/docker/gluster/gluster-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | # Description: Script to move the glusterfs initial setup to bind mounted directories of Atomic Host. 5 | # Copyright (c) 2016 Red Hat, Inc. 6 | # 7 | # This file is part of GlusterFS. 8 | # 9 | # This file is licensed to you under your choice of the GNU Lesser 10 | # General Public License, version 3 or any later version (LGPLv3 or 11 | # later), or the GNU General Public License, version 2 (GPLv2), in all 12 | # cases as published by the Free Software Foundation. 13 | ### 14 | 15 | main () { 16 | if test "$(ls /var/lib/heketi/fstab)" 17 | then 18 | mount -a --fstab /var/lib/heketi/fstab 19 | if [ $? -eq 1 ] 20 | then 21 | echo "mount failed" 22 | exit 1 23 | fi 24 | echo "Mount Successful" 25 | else 26 | echo "heketi-fstab not found" 27 | fi 28 | DIR_1="/etc/glusterfs" 29 | DIR_2="/var/log/glusterfs" 30 | DIR_3="/var/lib/glusterd" 31 | var=0 32 | for i in $DIR_1 $DIR_2 $DIR_3 33 | do 34 | if test "$(ls $i)" 35 | then 36 | echo "$i is not empty" 37 | var=$((var+1)) 38 | fi 39 | done 40 | 41 | if [ $var -eq 3 ] 42 | then 43 | exit 1 44 | fi 45 | 46 | cp -r /etc/glusterfs_bkp/* /etc/glusterfs 47 | if [ $? -eq 1 ] 48 | then 49 | echo "Failed to copy $DIR_1" 50 | exit 1 51 | fi 52 | 53 | cp -r /var/log/glusterfs_bkp/* /var/log/glusterfs 54 | if [ $? -eq 1 ] 55 | then 56 | echo "Failed to copy $DIR_2" 57 | exit 1 58 | fi 59 | 60 | cp -r /var/lib/glusterd_bkp/* /var/lib/glusterd 61 | if [ $? -eq 1 ] 62 | then 63 | echo "Failed to copy $DIR_3" 64 | exit 1 65 | fi 66 | 67 | echo "Script Ran Successfully" 68 | } 69 | main 70 | 71 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/config/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/config/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/config/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/roles/common/files/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestManyBricksVolume/vagrant/roles/common/files/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /apps/glusterfs/volume_durability_replica.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/heketi/heketi/executors" 14 | "github.com/heketi/heketi/pkg/glusterfs/api" 15 | ) 16 | 17 | type VolumeReplicaDurability struct { 18 | api.ReplicaDurability 19 | } 20 | 21 | func NewVolumeReplicaDurability(r *api.ReplicaDurability) *VolumeReplicaDurability { 22 | v := &VolumeReplicaDurability{} 23 | v.Replica = r.Replica 24 | 25 | return v 26 | } 27 | 28 | func (r *VolumeReplicaDurability) SetDurability() { 29 | if r.Replica == 0 { 30 | r.Replica = DEFAULT_REPLICA 31 | } 32 | } 33 | 34 | func (r *VolumeReplicaDurability) BrickSizeGenerator(size uint64) func() (int, uint64, error) { 35 | 36 | sets := 1 37 | return func() (int, uint64, error) { 38 | 39 | var brick_size uint64 40 | var num_sets int 41 | 42 | for { 43 | num_sets = sets 44 | sets *= 2 45 | brick_size = size / uint64(num_sets) 46 | 47 | if brick_size < BrickMinSize { 48 | return 0, 0, ErrMinimumBrickSize 49 | } else if brick_size <= BrickMaxSize { 50 | break 51 | } 52 | } 53 | 54 | return num_sets, brick_size, nil 55 | } 56 | } 57 | 58 | func (r *VolumeReplicaDurability) MinVolumeSize() uint64 { 59 | return BrickMinSize 60 | } 61 | 62 | func (r *VolumeReplicaDurability) BricksInSet() int { 63 | return r.Replica 64 | } 65 | 66 | func (r *VolumeReplicaDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { 67 | v.Type = executors.DurabilityReplica 68 | v.Replica = r.Replica 69 | } 70 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/files/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/files/insecure_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /pkg/heketitest/heketitest_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package heketitest 11 | 12 | import ( 13 | "testing" 14 | 15 | client "github.com/heketi/heketi/client/api/go-client" 16 | "github.com/heketi/tests" 17 | ) 18 | 19 | func TestNewHeketiMockTestServer(t *testing.T) { 20 | c := &HeketiMockTestServerConfig{ 21 | Auth: true, 22 | AdminKey: "admin", 23 | UserKey: "user", 24 | Logging: true, 25 | } 26 | 27 | h := NewHeketiMockTestServer(c) 28 | tests.Assert(t, h != nil) 29 | tests.Assert(t, h.Ts != nil) 30 | tests.Assert(t, h.DbFile != "") 31 | tests.Assert(t, h.App != nil) 32 | h.Close() 33 | 34 | h = NewHeketiMockTestServerDefault() 35 | tests.Assert(t, h != nil) 36 | tests.Assert(t, h.Ts != nil) 37 | tests.Assert(t, h.DbFile != "") 38 | tests.Assert(t, h.App != nil) 39 | } 40 | 41 | func TestHeketiMockTestServer(t *testing.T) { 42 | c := &HeketiMockTestServerConfig{ 43 | Auth: true, 44 | AdminKey: "admin", 45 | UserKey: "user", 46 | } 47 | 48 | h := NewHeketiMockTestServer(c) 49 | defer h.Close() 50 | 51 | api := client.NewClient(h.URL(), "admin", "admin") 52 | tests.Assert(t, api != nil) 53 | 54 | cluster, err := api.ClusterCreate() 55 | tests.Assert(t, err == nil) 56 | tests.Assert(t, cluster != nil) 57 | tests.Assert(t, len(cluster.Nodes) == 0) 58 | tests.Assert(t, len(cluster.Volumes) == 0) 59 | 60 | info, err := api.ClusterInfo(cluster.Id) 61 | tests.Assert(t, err == nil) 62 | tests.Assert(t, info.Id == cluster.Id) 63 | tests.Assert(t, len(info.Nodes) == 0) 64 | tests.Assert(t, len(info.Volumes) == 0) 65 | } 66 | -------------------------------------------------------------------------------- /client/api/go-client/topology.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), as published by the Free Software Foundation, 7 | // or under the Apache License, Version 2.0 . 9 | // 10 | // You may not use this file except in compliance with those terms. 11 | // 12 | 13 | package client 14 | 15 | import ( 16 | "github.com/heketi/heketi/pkg/glusterfs/api" 17 | ) 18 | 19 | func (c *Client) TopologyInfo() (*api.TopologyInfoResponse, error) { 20 | topo := &api.TopologyInfoResponse{ 21 | ClusterList: make([]api.Cluster, 0), 22 | } 23 | clusterlist, err := c.ClusterList() 24 | if err != nil { 25 | return nil, err 26 | } 27 | for _, cluster := range clusterlist.Clusters { 28 | clusteri, err := c.ClusterInfo(cluster) 29 | if err != nil { 30 | return nil, err 31 | } 32 | cluster := api.Cluster{ 33 | Id: clusteri.Id, 34 | Volumes: make([]api.VolumeInfoResponse, 0), 35 | Nodes: make([]api.NodeInfoResponse, 0), 36 | } 37 | cluster.Id = clusteri.Id 38 | 39 | // Iterate over the volume list in the cluster 40 | for _, volumes := range clusteri.Volumes { 41 | volumesi, err := c.VolumeInfo(volumes) 42 | if err != nil { 43 | return nil, err 44 | } 45 | if volumesi.Cluster == cluster.Id { 46 | cluster.Volumes = append(cluster.Volumes, *volumesi) 47 | } 48 | } 49 | 50 | // Iterate over the nodes in the cluster 51 | for _, node := range clusteri.Nodes { 52 | nodei, err := c.NodeInfo(string(node)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | cluster.Nodes = append(cluster.Nodes, *nodei) 57 | } 58 | topo.ClusterList = append(topo.ClusterList, cluster) 59 | } 60 | return topo, nil 61 | 62 | } 63 | -------------------------------------------------------------------------------- /apps/glusterfs/brick_create.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/boltdb/bolt" 14 | "github.com/heketi/heketi/executors" 15 | "github.com/heketi/heketi/pkg/utils" 16 | ) 17 | 18 | type CreateType int 19 | 20 | const ( 21 | CREATOR_CREATE CreateType = iota 22 | CREATOR_DESTROY 23 | ) 24 | 25 | func createDestroyConcurrently(db *bolt.DB, 26 | executor executors.Executor, 27 | brick_entries []*BrickEntry, 28 | create_type CreateType) error { 29 | 30 | sg := utils.NewStatusGroup() 31 | 32 | // Create a goroutine for each brick 33 | for _, brick := range brick_entries { 34 | sg.Add(1) 35 | go func(b *BrickEntry) { 36 | defer sg.Done() 37 | if create_type == CREATOR_CREATE { 38 | sg.Err(b.Create(db, executor)) 39 | } else { 40 | sg.Err(b.Destroy(db, executor)) 41 | } 42 | }(brick) 43 | } 44 | 45 | // Wait here until all goroutines have returned. If 46 | // any of errored, it would be cought here 47 | err := sg.Result() 48 | if err != nil { 49 | logger.Err(err) 50 | 51 | // Destroy all bricks and cleanup 52 | if create_type == CREATOR_CREATE { 53 | createDestroyConcurrently(db, executor, brick_entries, CREATOR_DESTROY) 54 | } 55 | } 56 | return err 57 | } 58 | 59 | func CreateBricks(db *bolt.DB, executor executors.Executor, brick_entries []*BrickEntry) error { 60 | return createDestroyConcurrently(db, executor, brick_entries, CREATOR_CREATE) 61 | } 62 | 63 | func DestroyBricks(db *bolt.DB, executor executors.Executor, brick_entries []*BrickEntry) error { 64 | return createDestroyConcurrently(db, executor, brick_entries, CREATOR_DESTROY) 65 | } 66 | -------------------------------------------------------------------------------- /apps/glusterfs/dbentry_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/boltdb/bolt" 14 | "github.com/heketi/tests" 15 | "os" 16 | "testing" 17 | "time" 18 | ) 19 | 20 | type testDbEntry struct { 21 | } 22 | 23 | func (t *testDbEntry) BucketName() string { 24 | return "TestBucket" 25 | } 26 | 27 | func (t *testDbEntry) Marshal() ([]byte, error) { 28 | return nil, nil 29 | } 30 | 31 | func (t *testDbEntry) Unmarshal(data []byte) error { 32 | return nil 33 | } 34 | 35 | func TestEntryRegister(t *testing.T) { 36 | tmpfile := tests.Tempfile() 37 | 38 | // Setup BoltDB database 39 | db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) 40 | tests.Assert(t, err == nil) 41 | defer os.Remove(tmpfile) 42 | 43 | // Create a bucket 44 | entry := &testDbEntry{} 45 | err = db.Update(func(tx *bolt.Tx) error { 46 | 47 | // Create Cluster Bucket 48 | _, err := tx.CreateBucketIfNotExists([]byte(entry.BucketName())) 49 | tests.Assert(t, err == nil) 50 | 51 | // Register a value 52 | _, err = EntryRegister(tx, entry, "mykey", []byte("myvalue")) 53 | tests.Assert(t, err == nil) 54 | 55 | return nil 56 | }) 57 | tests.Assert(t, err == nil) 58 | 59 | // Try to write key again 60 | err = db.Update(func(tx *bolt.Tx) error { 61 | 62 | // Save again, it should not work 63 | val, err := EntryRegister(tx, entry, "mykey", []byte("myvalue")) 64 | tests.Assert(t, err == ErrKeyExists) 65 | tests.Assert(t, string(val) == "myvalue") 66 | 67 | // Remove key 68 | err = EntryDelete(tx, entry, "mykey") 69 | tests.Assert(t, err == nil) 70 | 71 | // Register again 72 | _, err = EntryRegister(tx, entry, "mykey", []byte("myvalue")) 73 | tests.Assert(t, err == nil) 74 | 75 | return nil 76 | }) 77 | tests.Assert(t, err == nil) 78 | 79 | } 80 | -------------------------------------------------------------------------------- /apps/glusterfs/volume_durability_ec.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/heketi/heketi/executors" 14 | "github.com/heketi/heketi/pkg/glusterfs/api" 15 | ) 16 | 17 | type VolumeDisperseDurability struct { 18 | api.DisperseDurability 19 | } 20 | 21 | func NewVolumeDisperseDurability(d *api.DisperseDurability) *VolumeDisperseDurability { 22 | v := &VolumeDisperseDurability{} 23 | v.Data = d.Data 24 | v.Redundancy = d.Redundancy 25 | 26 | return v 27 | } 28 | 29 | func (d *VolumeDisperseDurability) SetDurability() { 30 | if d.Data == 0 { 31 | d.Data = DEFAULT_EC_DATA 32 | } 33 | if d.Redundancy == 0 { 34 | d.Redundancy = DEFAULT_EC_REDUNDANCY 35 | } 36 | } 37 | 38 | func (d *VolumeDisperseDurability) BrickSizeGenerator(size uint64) func() (int, uint64, error) { 39 | 40 | sets := 1 41 | return func() (int, uint64, error) { 42 | 43 | var brick_size uint64 44 | var num_sets int 45 | 46 | for { 47 | num_sets = sets 48 | sets *= 2 49 | brick_size = size / uint64(num_sets) 50 | 51 | // Divide what would be the brick size for replica by the 52 | // number of data drives in the disperse request 53 | brick_size /= uint64(d.Data) 54 | 55 | if brick_size < BrickMinSize { 56 | return 0, 0, ErrMinimumBrickSize 57 | } else if brick_size <= BrickMaxSize { 58 | break 59 | } 60 | } 61 | 62 | return num_sets, brick_size, nil 63 | } 64 | } 65 | 66 | func (d *VolumeDisperseDurability) MinVolumeSize() uint64 { 67 | return BrickMinSize * uint64(d.Data) 68 | } 69 | 70 | func (d *VolumeDisperseDurability) BricksInSet() int { 71 | return d.Data + d.Redundancy 72 | } 73 | 74 | func (d *VolumeDisperseDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { 75 | v.Type = executors.DurabilityDispersion 76 | v.Data = d.Data 77 | v.Redundancy = d.Redundancy 78 | } 79 | -------------------------------------------------------------------------------- /executors/sshexec/peer.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package sshexec 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/lpabon/godbc" 16 | ) 17 | 18 | // :TODO: Rename this function to NodeInit or something 19 | func (s *SshExecutor) PeerProbe(host, newnode string) error { 20 | 21 | godbc.Require(host != "") 22 | godbc.Require(newnode != "") 23 | 24 | logger.Info("Probing: %v -> %v", host, newnode) 25 | // create the commands 26 | commands := []string{ 27 | fmt.Sprintf("gluster peer probe %v", newnode), 28 | } 29 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | // Determine if there is a snapshot limit configuration setting 35 | if s.RemoteExecutor.SnapShotLimit() > 0 { 36 | logger.Info("Setting snapshot limit") 37 | commands = []string{ 38 | fmt.Sprintf("gluster --mode=script snapshot config snap-max-hard-limit %v", 39 | s.RemoteExecutor.SnapShotLimit()), 40 | } 41 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) 42 | if err != nil { 43 | return err 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (s *SshExecutor) PeerDetach(host, detachnode string) error { 51 | godbc.Require(host != "") 52 | godbc.Require(detachnode != "") 53 | 54 | // create the commands 55 | logger.Info("Detaching node %v", detachnode) 56 | commands := []string{ 57 | fmt.Sprintf("gluster peer detach %v", detachnode), 58 | } 59 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) 60 | if err != nil { 61 | logger.Err(err) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (s *SshExecutor) GlusterdCheck(host string) error { 68 | godbc.Require(host != "") 69 | 70 | logger.Info("Check Glusterd service status in node %v", host) 71 | commands := []string{ 72 | fmt.Sprintf("systemctl status glusterd"), 73 | } 74 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) 75 | if err != nil { 76 | logger.Err(err) 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /tests/functional/TestSmokeTest/vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | 5 | NODES = 4 6 | DISKS = 8 7 | 8 | Vagrant.configure("2") do |config| 9 | config.ssh.insert_key = false 10 | 11 | config.vm.provider :libvirt do |v,override| 12 | override.vm.box = "centos/7" 13 | override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true 14 | end 15 | config.vm.provider :virtualbox do |v| 16 | config.vm.box = "bento/centos-7.1" 17 | end 18 | 19 | # Make the glusterfs cluster, each with DISKS number of drives 20 | (0..NODES-1).each do |i| 21 | config.vm.define "storage#{i}" do |storage| 22 | storage.vm.hostname = "storage#{i}" 23 | storage.vm.network :private_network, ip: "192.168.10.10#{i}" 24 | (0..DISKS-1).each do |d| 25 | storage.vm.provider :virtualbox do |vb| 26 | vb.customize [ "createhd", "--filename", "disk-#{i}-#{d}.vdi", "--size", 500*1024 ] 27 | vb.customize [ "storageattach", :id, "--storagectl", "SATA Controller", "--port", 3+d, "--device", 0, "--type", "hdd", "--medium", "disk-#{i}-#{d}.vdi" ] 28 | vb.memory = 1024 29 | vb.cpus = 2 30 | end 31 | driverletters = ('b'..'z').to_a 32 | storage.vm.provider :libvirt do |lv| 33 | lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_smoke_disk-#{i}-#{d}.disk", :size => '500G' 34 | lv.memory = 1024 35 | lv.cpus =2 36 | end 37 | end 38 | 39 | if i == (NODES-1) 40 | # View the documentation for the provider you're using for more 41 | # information on available options. 42 | storage.vm.provision :ansible do |ansible| 43 | ansible.limit = "all" 44 | ansible.playbook = "site.yml" 45 | ansible.groups = { 46 | "client" => ["client"], 47 | "heketi" => ["storage0"], 48 | "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, 49 | } 50 | 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /etc/heketi.json: -------------------------------------------------------------------------------- 1 | { 2 | "_port_comment": "Heketi Server Port Number", 3 | "port": "8080", 4 | 5 | "_use_auth": "Enable JWT authorization. Please enable for deployment", 6 | "use_auth": false, 7 | 8 | "_jwt": "Private keys for access", 9 | "jwt": { 10 | "_admin": "Admin has access to all APIs", 11 | "admin": { 12 | "key": "My Secret" 13 | }, 14 | "_user": "User only has access to /volumes endpoint", 15 | "user": { 16 | "key": "My Secret" 17 | } 18 | }, 19 | 20 | "_backup_db_to_kube_secret": "Backup the heketi database to a Kubernetes secret when running in Kubernetes. Default is off.", 21 | "backup_db_to_kube_secret": false, 22 | 23 | "_glusterfs_comment": "GlusterFS Configuration", 24 | "glusterfs": { 25 | "_executor_comment": [ 26 | "Execute plugin. Possible choices: mock, ssh", 27 | "mock: This setting is used for testing and development.", 28 | " It will not send commands to any node.", 29 | "ssh: This setting will notify Heketi to ssh to the nodes.", 30 | " It will need the values in sshexec to be configured.", 31 | "kubernetes: Communicate with GlusterFS containers over", 32 | " Kubernetes exec api." 33 | ], 34 | "executor": "mock", 35 | 36 | "_sshexec_comment": "SSH username and private key file information", 37 | "sshexec": { 38 | "keyfile": "path/to/private_key", 39 | "user": "sshuser", 40 | "port": "Optional: ssh port. Default is 22", 41 | "fstab": "Optional: Specify fstab file on node. Default is /etc/fstab" 42 | }, 43 | 44 | "_kubeexec_comment": "Kubernetes configuration", 45 | "kubeexec": { 46 | "host" :"https://kubernetes.host:8443", 47 | "cert" : "/path/to/crt.file", 48 | "insecure": false, 49 | "user": "kubernetes username", 50 | "password": "password for kubernetes user", 51 | "namespace": "OpenShift project or Kubernetes namespace", 52 | "fstab": "Optional: Specify fstab file on node. Default is /etc/fstab" 53 | }, 54 | 55 | "_db_comment": "Database file name", 56 | "db": "/var/lib/heketi/heketi.db", 57 | 58 | "_loglevel_comment": [ 59 | "Set log level. Choices are:", 60 | " none, critical, error, warning, info, debug", 61 | "Default is warning" 62 | ], 63 | "loglevel" : "debug" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /extras/openshift/templates/README.md: -------------------------------------------------------------------------------- 1 | # Create a Heketi service in OpenShift 2 | > NOTE: This template file places the database in an _EmptyDir_ volume. You will need to adjust accordingly if you would like the database to be on reliable persistent storage. 3 | 4 | * Register template with OpenShift 5 | 6 | ``` 7 | oc create -f heketi.json 8 | ``` 9 | 10 | * Note the number of parameters which need to be set. Currently only _NAME_ 11 | needs to be set. 12 | 13 | ``` 14 | oc process --parameters heketi 15 | ``` 16 | 17 | * Deploy a Heketi service 18 | 19 | Here is an example of how to deploy Heketi 20 | 21 | ``` 22 | oc process heketi -v NAME=myheketiservice \ 23 | HEKETI_KUBE_NAMESPACE=test \ 24 | HEKETI_KUBE_APIHOST='https://192.168.10.90:8443' \ 25 | HEKETI_KUBE_INSECURE=y \ 26 | HEKETI_KUBE_USER=test-admin \ 27 | HEKETI_KUBE_PASSWORD=admin | oc create -f - 28 | ``` 29 | 30 | * Note service 31 | 32 | ``` 33 | oc status 34 | ``` 35 | 36 | * Send a _hello_ command to service 37 | 38 | ``` 39 | curl http://:/hello 40 | ``` 41 | 42 | * For example 43 | 44 | ``` 45 | $ oc project 46 | Using project "gluster" on server "https://192.168.10.90:8443". 47 | 48 | $ oc create -f heketi-template.json 49 | template "heketi" created 50 | 51 | $ oc process heketi -v NAME=ams \ 52 | > HEKETI_KUBE_NAMESPACE=gluster \ 53 | > HEKETI_KUBE_APIHOST='https://192.168.10.90:8443' \ 54 | > HEKETI_KUBE_INSECURE=y \ 55 | > HEKETI_KUBE_USER=test-admin \ 56 | > HEKETI_KUBE_PASSWORD=admin | oc create -f - 57 | service "ams" created 58 | deploymentconfig "ams" created 59 | 60 | $ oc status 61 | In project gluster on server https://192.168.10.90:8443 62 | 63 | svc/ams - 172.30.244.79:8080 64 | dc/ams deploys docker.io/heketi/heketi:dev 65 | deployment #1 pending 5 seconds ago 66 | 67 | View details with 'oc describe /' or list everything with 'oc get all'. 68 | 69 | $ oc get pods -o wide 70 | NAME READY STATUS RESTARTS AGE NODE 71 | ams-1-bed48 0/1 ContainerCreating 0 8s openshift-node-1 72 | ams-1-deploy 1/1 Running 0 1m openshift-node-2 73 | 74 | << Wait until the container is running, then... >> 75 | 76 | $ curl http://172.30.244.79:8080/hello 77 | HelloWorld from GlusterFS Application 78 | ``` 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /apps/glusterfs/app_middleware.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "net/http" 14 | "strings" 15 | 16 | jwt "github.com/dgrijalva/jwt-go" 17 | "github.com/gorilla/context" 18 | "github.com/urfave/negroni" 19 | 20 | "github.com/heketi/heketi/pkg/kubernetes" 21 | ) 22 | 23 | var ( 24 | kubeBackupDbToSecret = kubernetes.KubeBackupDbToSecret 25 | ) 26 | 27 | // Authorization function 28 | func (a *App) Auth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 29 | 30 | // Value saved by the JWT middleware. 31 | data := context.Get(r, "jwt") 32 | 33 | // Need to change from interface{} to the jwt.Token type 34 | token := data.(*jwt.Token) 35 | claims := token.Claims.(jwt.MapClaims) 36 | 37 | // Check access 38 | if "user" == claims["iss"] && r.URL.Path != "/volumes" { 39 | http.Error(w, "Administrator access required", http.StatusUnauthorized) 40 | return 41 | } 42 | 43 | // Everything is clean 44 | next(w, r) 45 | } 46 | 47 | // Backup database to a secret 48 | func (a *App) BackupToKubernetesSecret( 49 | w http.ResponseWriter, 50 | r *http.Request, 51 | next http.HandlerFunc) { 52 | 53 | // Call the next middleware first 54 | // Wrap it in a negroni ResponseWriter because for some reason 55 | // the Golang http ResponseWriter does not provide access to 56 | // the HttpStatus. 57 | responsew := negroni.NewResponseWriter(w) 58 | next(responsew, r) 59 | 60 | // Backup for everything except GET methods which do not 61 | // provide information on asynchronous completion request 62 | if !a.isAsyncDone(responsew, r) && r.Method == http.MethodGet { 63 | return 64 | } 65 | 66 | // Backup database 67 | err := kubeBackupDbToSecret(a.db) 68 | if err != nil { 69 | logger.Err(err) 70 | } else { 71 | logger.Info("Backup successful") 72 | } 73 | } 74 | 75 | func (a *App) isAsyncDone( 76 | w negroni.ResponseWriter, 77 | r *http.Request) bool { 78 | 79 | return r.Method == http.MethodGet && 80 | strings.HasPrefix(r.URL.Path, ASYNC_ROUTE) && 81 | (w.Status() == http.StatusNoContent || 82 | w.Status() == http.StatusSeeOther) 83 | } 84 | -------------------------------------------------------------------------------- /extras/docker/fromsource/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | The main purpose of this container is to be used for testing 3 | and verification of the unstable master builds. 4 | 5 | # How to use for testing 6 | 7 | ## Downloading 8 | First you will need to download the latest development container: 9 | 10 | # docker pull heketi/heketi:dev 11 | 12 | > NOTE: Most likely you will always need to do a new pull before staring your tests since the container changes so often. 13 | 14 | ## Server Setup 15 | You will need to create a directory which has a directory containing configuraiton and any private key if necessary, and an empty directory used for storing the database. Directory and files must be read/write by user with id 1000 and if an ssh private key is used, it must also have a mod of 0600. 16 | 17 | Here is an example: 18 | 19 | $ mkdir -p heketi/config 20 | $ mkdir -p heketi/db 21 | $ cp heketi.json heketi/config 22 | $ cp myprivate_key heketi/config 23 | $ chmod 600 heketi/config/myprivate_key 24 | $ chown 1000:1000 -R heketi 25 | 26 | To run: 27 | 28 | # docker run -d -p 8080:8080 \ 29 | -v $PWD/heketi/config:/etc/heketi \ 30 | -v $PWD/heketi/db:/var/lib/heketi \ 31 | heketi/heketi:dev 32 | 33 | Now you can see the container running. Here is an example: 34 | 35 | ``` 36 | $ sudo docker ps 37 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 38 | 6e3ed5c59f87 heketidev "/usr/bin/heketi -con" 32 minutes ago Up 32 minutes 0.0.0.0:8080->8080/tcp goofy_kowalevski 39 | ``` 40 | 41 | Now we can check the logs 42 | 43 | ``` 44 | $ sudo docker logs 6e3ed5c59f87 | head 45 | Heketi 1.0.0-81-g0c78700 46 | [heketi] INFO 2016/04/12 18:57:13 Loaded ssh executor 47 | [heketi] INFO 2016/04/12 18:57:13 Loaded simple allocator 48 | [heketi] INFO 2016/04/12 18:57:13 GlusterFS Application Loaded 49 | [negroni] Started GET /hello 50 | [negroni] Completed 200 OK in 79.951µs 51 | [negroni] Started GET /clusters 52 | [negroni] Completed 200 OK in 91.658µs 53 | [negroni] Started POST /clusters 54 | [negroni] Completed 201 Created in 6.046309ms 55 | ``` 56 | 57 | ## Using heketi-cli 58 | Using our example above, to use the heketi-cli, you can type: 59 | 60 | ``` 61 | $ sudo docker exec 6e3ed5c59f87 \ 62 | heketi-cli -h 63 | $ sudo docker exec 6e3ed5c59f87 \ 64 | heketi-cli --server http://localhost:8080/ cluster list 65 | ``` 66 | 67 | # Build 68 | If you need to build it: 69 | 70 | # docker build --rm --tag /heketi:dev . 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stories in Ready](https://badge.waffle.io/heketi/heketi.png?label=in%20progress&title=In%20Progress)](https://waffle.io/heketi/heketi) 2 | [![Build Status](https://travis-ci.org/heketi/heketi.svg?branch=master)](https://travis-ci.org/heketi/heketi) 3 | [![Coverage Status](https://coveralls.io/repos/heketi/heketi/badge.svg)](https://coveralls.io/r/heketi/heketi) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/heketi/heketi)](https://goreportcard.com/report/github.com/heketi/heketi) 5 | [![Join the chat at https://gitter.im/heketi/heketi](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/heketi/heketi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # Heketi 8 | Heketi provides a RESTful management interface which can be used to manage the life cycle of GlusterFS volumes. With Heketi, cloud services like OpenStack Manila, Kubernetes, and OpenShift can dynamically provision GlusterFS volumes with any of the supported durability types. Heketi will automatically determine the location for bricks across the cluster, making sure to place bricks and its replicas across different failure domains. Heketi also supports any number of GlusterFS clusters, allowing cloud services to provide network file storage without being limited to a single GlusterFS cluster. 9 | 10 | # Workflow 11 | When a request is received to create a volume, Heketi will first allocate the appropriate storage in a cluster, making sure to place brick replicas across failure domains. It will then format, then mount the storage to create bricks for the volume requested. Once all bricks have been automatically created, Heketi will finally satisfy the request by creating, then starting the newly created GlusterFS volume. 12 | 13 | # Downloads 14 | Please go to the [wiki/Installation](https://github.com/heketi/heketi/wiki/Installation) for more information 15 | 16 | # Documentation 17 | Please visit the [WIKI](http://github.com/heketi/heketi/wiki) for project documentation and demo information 18 | 19 | # Demo 20 | Please visit [Vagrant-Heketi](https://github.com/heketi/vagrant-heketi) to try out the demo. 21 | 22 | # Community 23 | 24 | * Mailing list: [Join our mailing list](http://lists.gluster.org/mailman/listinfo/heketi-devel) 25 | * IRC: #heketi on Freenode 26 | 27 | # Talks 28 | 29 | * DevNation 2016 30 | 31 | [![image](https://img.youtube.com/vi/gmEUnOmDziQ/3.jpg)](https://youtu.be/gmEUnOmDziQ) 32 | [Slides](http://bit.ly/29avBJX) 33 | 34 | * Devconf.cz 2016: 35 | 36 | [![image](https://img.youtube.com/vi/jpkG4wciy4U/3.jpg)](https://www.youtube.com/watch?v=jpkG4wciy4U) [Slides](https://github.com/lpabon/go-slides) 37 | 38 | -------------------------------------------------------------------------------- /client/cli/go/cmds/root.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package cmds 11 | 12 | import ( 13 | "fmt" 14 | "io" 15 | "os" 16 | 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var ( 21 | HEKETI_CLI_VERSION = "(dev)" 22 | stderr io.Writer 23 | stdout io.Writer 24 | options Options 25 | version bool 26 | ) 27 | 28 | // Main arguments 29 | type Options struct { 30 | Url, Key, User string 31 | Json bool 32 | } 33 | 34 | var RootCmd = &cobra.Command{ 35 | Use: "heketi-cli", 36 | Short: "Command line program for Heketi", 37 | Long: "Command line program for Heketi", 38 | Example: ` $ export HEKETI_CLI_SERVER=http://localhost:8080 39 | $ heketi-cli volume list`, 40 | Run: func(cmd *cobra.Command, args []string) { 41 | if version { 42 | fmt.Printf("heketi-cli %v\n", HEKETI_CLI_VERSION) 43 | } else { 44 | cmd.Usage() 45 | } 46 | }, 47 | } 48 | 49 | func init() { 50 | cobra.OnInitialize(initConfig) 51 | RootCmd.PersistentFlags().StringVarP(&options.Url, "server", "s", "", 52 | "\n\tHeketi server. Can also be set using the"+ 53 | "\n\tenvironment variable HEKETI_CLI_SERVER") 54 | RootCmd.PersistentFlags().StringVar(&options.Key, "secret", "", 55 | "\n\tSecret key for specified user. Can also be"+ 56 | "\n\tset using the environment variable HEKETI_CLI_KEY") 57 | RootCmd.PersistentFlags().StringVar(&options.User, "user", "", 58 | "\n\tHeketi user. Can also be set using the"+ 59 | "\n\tenvironment variable HEKETI_CLI_USER") 60 | RootCmd.PersistentFlags().BoolVar(&options.Json, "json", false, 61 | "\n\tPrint response as JSON") 62 | RootCmd.Flags().BoolVarP(&version, "version", "v", false, 63 | "\n\tPrint version") 64 | RootCmd.SilenceUsage = true 65 | } 66 | 67 | func initConfig() { 68 | // Check server 69 | if options.Url == "" { 70 | options.Url = os.Getenv("HEKETI_CLI_SERVER") 71 | args := os.Args[1:] 72 | if options.Url == "" && !version && len(args) > 0 { 73 | fmt.Fprintf(stderr, "Server must be provided\n") 74 | os.Exit(3) 75 | } 76 | } 77 | 78 | // Check user 79 | if options.Key == "" { 80 | options.Key = os.Getenv("HEKETI_CLI_KEY") 81 | } 82 | 83 | // Check key 84 | if options.User == "" { 85 | options.User = os.Getenv("HEKETI_CLI_USER") 86 | } 87 | } 88 | 89 | func NewHeketiCli(heketiVersion string, mstderr io.Writer, mstdout io.Writer) *cobra.Command { 90 | stderr = mstderr 91 | stdout = mstdout 92 | HEKETI_CLI_VERSION = heketiVersion 93 | return RootCmd 94 | } 95 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/mock-topology.json: -------------------------------------------------------------------------------- 1 | { 2 | "clusters": [ 3 | { 4 | "nodes": [ 5 | { 6 | "node": { 7 | "hostnames": { 8 | "manage": [ 9 | "192.168.10.100" 10 | ], 11 | "storage": [ 12 | "192.168.10.100" 13 | ] 14 | }, 15 | "zone": 1 16 | }, 17 | "devices": [ 18 | "/dev/sdb", 19 | "/dev/sdc", 20 | "/dev/sdd" 21 | ] 22 | }, 23 | { 24 | "node": { 25 | "hostnames": { 26 | "manage": [ 27 | "192.168.10.101" 28 | ], 29 | "storage": [ 30 | "192.168.10.101" 31 | ] 32 | }, 33 | "zone": 2 34 | }, 35 | "devices": [ 36 | "/dev/sdb", 37 | "/dev/sdc", 38 | "/dev/sdd" 39 | ] 40 | }, 41 | { 42 | "node": { 43 | "hostnames": { 44 | "manage": [ 45 | "192.168.10.102" 46 | ], 47 | "storage": [ 48 | "192.168.10.102" 49 | ] 50 | }, 51 | "zone": 1 52 | }, 53 | "devices": [ 54 | "/dev/sdb", 55 | "/dev/sdc", 56 | "/dev/sdd" 57 | ] 58 | }, 59 | { 60 | "node": { 61 | "hostnames": { 62 | "manage": [ 63 | "192.168.10.103" 64 | ], 65 | "storage": [ 66 | "192.168.10.103" 67 | ] 68 | }, 69 | "zone": 2 70 | }, 71 | "devices": [ 72 | "/dev/sdb", 73 | "/dev/sdc", 74 | "/dev/sdd" 75 | ] 76 | } 77 | ] 78 | } 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /pkg/utils/sortedstrings_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "github.com/heketi/tests" 14 | "sort" 15 | "testing" 16 | ) 17 | 18 | func TestSortedStringsHas(t *testing.T) { 19 | s := sort.StringSlice{"z", "b", "a"} 20 | s.Sort() 21 | tests.Assert(t, len(s) == 3) 22 | tests.Assert(t, s[0] == "a") 23 | tests.Assert(t, s[1] == "b") 24 | tests.Assert(t, s[2] == "z") 25 | 26 | tests.Assert(t, SortedStringHas(s, "a")) 27 | tests.Assert(t, SortedStringHas(s, "b")) 28 | tests.Assert(t, SortedStringHas(s, "z")) 29 | tests.Assert(t, !SortedStringHas(s, "c")) 30 | tests.Assert(t, !SortedStringHas(s, "zz")) 31 | } 32 | 33 | func TestSortedStringsDelete(t *testing.T) { 34 | s := sort.StringSlice{"z", "b", "a"} 35 | s.Sort() 36 | tests.Assert(t, len(s) == 3) 37 | tests.Assert(t, s[0] == "a") 38 | tests.Assert(t, s[1] == "b") 39 | tests.Assert(t, s[2] == "z") 40 | 41 | tests.Assert(t, SortedStringHas(s, "a")) 42 | tests.Assert(t, SortedStringHas(s, "b")) 43 | tests.Assert(t, SortedStringHas(s, "z")) 44 | tests.Assert(t, !SortedStringHas(s, "c")) 45 | tests.Assert(t, !SortedStringHas(s, "zz")) 46 | 47 | s = SortedStringsDelete(s, "notthere") 48 | tests.Assert(t, len(s) == 3) 49 | s = SortedStringsDelete(s, "zzzznotthere") 50 | tests.Assert(t, len(s) == 3) 51 | s = SortedStringsDelete(s, "1azzzznotthere") 52 | tests.Assert(t, len(s) == 3) 53 | tests.Assert(t, SortedStringHas(s, "a")) 54 | tests.Assert(t, SortedStringHas(s, "b")) 55 | tests.Assert(t, SortedStringHas(s, "z")) 56 | tests.Assert(t, !SortedStringHas(s, "c")) 57 | tests.Assert(t, !SortedStringHas(s, "zz")) 58 | 59 | s = SortedStringsDelete(s, "z") 60 | tests.Assert(t, len(s) == 2) 61 | tests.Assert(t, SortedStringHas(s, "a")) 62 | tests.Assert(t, SortedStringHas(s, "b")) 63 | tests.Assert(t, !SortedStringHas(s, "z")) 64 | tests.Assert(t, !SortedStringHas(s, "c")) 65 | tests.Assert(t, !SortedStringHas(s, "zz")) 66 | 67 | s = SortedStringsDelete(s, "a") 68 | tests.Assert(t, len(s) == 1) 69 | tests.Assert(t, !SortedStringHas(s, "a")) 70 | tests.Assert(t, SortedStringHas(s, "b")) 71 | tests.Assert(t, !SortedStringHas(s, "z")) 72 | tests.Assert(t, !SortedStringHas(s, "c")) 73 | tests.Assert(t, !SortedStringHas(s, "zz")) 74 | 75 | s = SortedStringsDelete(s, "b") 76 | tests.Assert(t, len(s) == 0) 77 | tests.Assert(t, !SortedStringHas(s, "a")) 78 | tests.Assert(t, !SortedStringHas(s, "b")) 79 | tests.Assert(t, !SortedStringHas(s, "z")) 80 | tests.Assert(t, !SortedStringHas(s, "c")) 81 | tests.Assert(t, !SortedStringHas(s, "zz")) 82 | 83 | } 84 | -------------------------------------------------------------------------------- /extras/docker/gluster/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos 2 | 3 | MAINTAINER Humble Chirammal hchiramm@redhat.com 4 | LABEL version="0.5" 5 | LABEL description="GlusterFS container based on CentOS 7" 6 | 7 | ENV container docker 8 | 9 | RUN yum --setopt=tsflags=nodocs -y update; yum clean all; 10 | 11 | RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ 12 | rm -f /lib/systemd/system/multi-user.target.wants/*;\ 13 | rm -f /etc/systemd/system/*.wants/*;\ 14 | rm -f /lib/systemd/system/local-fs.target.wants/*; \ 15 | rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ 16 | rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ 17 | rm -f /lib/systemd/system/basic.target.wants/*;\ 18 | rm -f /lib/systemd/system/anaconda.target.wants/*; 19 | 20 | RUN yum --setopt=tsflags=nodocs -q -y install \ 21 | wget \ 22 | nfs-utils \ 23 | attr \ 24 | iputils \ 25 | iproute \ 26 | sudo \ 27 | xfsprogs \ 28 | centos-release-gluster \ 29 | ntp \ 30 | epel-release \ 31 | openssh-clients \ 32 | cronie \ 33 | tar \ 34 | rsync \ 35 | sos ; yum clean all 36 | 37 | RUN yum --setopt=tsflags=nodocs -y install \ 38 | glusterfs \ 39 | glusterfs-server \ 40 | glusterfs-geo-replication ; yum clean all 41 | 42 | # Backing up gluster config as it overlaps when bind mounting. 43 | RUN mkdir -p /etc/glusterfs_bkp /var/lib/glusterd_bkp /var/log/glusterfs_bkp;\ 44 | cp -r /etc/glusterfs/* /etc/glusterfs_bkp;\ 45 | cp -r /var/lib/glusterd/* /var/lib/glusterd_bkp;\ 46 | cp -r /var/log/glusterfs/* /var/log/glusterfs_bkp; 47 | 48 | # Adding script to move the glusterfs config file to location 49 | ADD gluster-setup.service /etc/systemd/system/gluster-setup.service 50 | RUN chmod 644 /etc/systemd/system/gluster-setup.service 51 | 52 | # Adding script to move the glusterfs config file to location 53 | ADD gluster-setup.sh /usr/sbin/gluster-setup.sh 54 | RUN chmod 500 /usr/sbin/gluster-setup.sh 55 | 56 | # To avoid the warnings while accessing the container 57 | RUN sed -i "s/LANG/\#LANG/g" /etc/locale.conf 58 | 59 | # Configure LVM so that we can create LVs and snapshots 60 | RUN sed -i.save -e "s#udev_sync = 1#udev_sync = 0#" \ 61 | -e "s#udev_rules = 1#udev_rules = 0#" \ 62 | -e "s#use_lvmetad = 1#use_lvmetad = 0#" /etc/lvm/lvm.conf 63 | 64 | # Set password 65 | RUN echo 'root:password' | chpasswd 66 | 67 | # Set SSH public key 68 | USER root 69 | 70 | VOLUME [ "/sys/fs/cgroup", "/dev", "/run/lvm" , "/var/lib/heketi" ] 71 | 72 | EXPOSE 111 245 443 24007 2049 8080 6010 6011 6012 38465 38466 38468 38469 49152 49153 49154 49156 49157 49158 49159 49160 49161 49162 73 | 74 | RUN systemctl disable nfs-server.service 75 | RUN systemctl enable rpcbind.service 76 | RUN systemctl enable ntpd.service 77 | RUN systemctl enable gluster-setup.service 78 | RUN systemctl enable glusterd.service 79 | 80 | CMD ["/usr/sbin/init"] 81 | -------------------------------------------------------------------------------- /apps/glusterfs/volume_entry_create.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "fmt" 14 | "strings" 15 | 16 | "github.com/boltdb/bolt" 17 | "github.com/heketi/heketi/executors" 18 | "github.com/heketi/heketi/pkg/utils" 19 | "github.com/lpabon/godbc" 20 | ) 21 | 22 | func (v *VolumeEntry) createVolume(db *bolt.DB, 23 | executor executors.Executor, 24 | brick_entries []*BrickEntry) error { 25 | 26 | godbc.Require(db != nil) 27 | godbc.Require(brick_entries != nil) 28 | 29 | // Create a volume request for executor with 30 | // the bricks allocated 31 | vr, host, err := v.createVolumeRequest(db, brick_entries) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // Create the volume 37 | _, err = executor.VolumeCreate(host, vr) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | // Get all brick hosts 43 | stringset := utils.NewStringSet() 44 | for _, brick := range vr.Bricks { 45 | stringset.Add(brick.Host) 46 | } 47 | hosts := stringset.Strings() 48 | v.Info.Mount.GlusterFS.Hosts = hosts 49 | 50 | // Save volume information 51 | v.Info.Mount.GlusterFS.MountPoint = fmt.Sprintf("%v:%v", 52 | hosts[0], vr.Name) 53 | 54 | // Set glusterfs mount volfile-servers options 55 | v.Info.Mount.GlusterFS.Options = make(map[string]string) 56 | v.Info.Mount.GlusterFS.Options["backup-volfile-servers"] = 57 | strings.Join(hosts[1:], ",") 58 | 59 | godbc.Ensure(v.Info.Mount.GlusterFS.MountPoint != "") 60 | return nil 61 | } 62 | 63 | func (v *VolumeEntry) createVolumeRequest(db *bolt.DB, 64 | brick_entries []*BrickEntry) (*executors.VolumeRequest, string, error) { 65 | godbc.Require(db != nil) 66 | godbc.Require(brick_entries != nil) 67 | 68 | // Setup list of bricks 69 | vr := &executors.VolumeRequest{} 70 | vr.Bricks = make([]executors.BrickInfo, len(brick_entries)) 71 | var sshhost string 72 | for i, b := range brick_entries { 73 | 74 | // Setup path 75 | vr.Bricks[i].Path = b.Info.Path 76 | 77 | // Get storage host name from Node entry 78 | err := db.View(func(tx *bolt.Tx) error { 79 | node, err := NewNodeEntryFromId(tx, b.Info.NodeId) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | if sshhost == "" { 85 | sshhost = node.ManageHostName() 86 | } 87 | vr.Bricks[i].Host = node.StorageHostName() 88 | godbc.Check(vr.Bricks[i].Host != "") 89 | 90 | return nil 91 | }) 92 | if err != nil { 93 | logger.Err(err) 94 | return nil, "", err 95 | } 96 | } 97 | 98 | // Setup volume information in the request 99 | vr.Name = v.Info.Name 100 | v.Durability.SetExecutorVolumeRequest(vr) 101 | vr.GlusterVolumeOptions = v.GlusterVolumeOptions 102 | 103 | return vr, sshhost, nil 104 | } 105 | -------------------------------------------------------------------------------- /extras/etc/init/heketi.initd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # heketi Startup script for the heketi server 4 | # 5 | # chkconfig: - 95 95 6 | # description: Heketi service 7 | 8 | ### BEGIN INIT INFO 9 | # Provides: heketi 10 | # Required-Start: $local_fs $network 11 | # Required-Stop: $local_fs $network 12 | # Should-Start: 13 | # Should-Stop: 14 | # Default-Start: 2 3 4 5 15 | # Default-Stop: 0 1 6 16 | # Short-Description: start and stop heketi server 17 | # Description: Heketi server 18 | ### END INIT INFO 19 | 20 | # Source function library. 21 | . /etc/rc.d/init.d/functions 22 | 23 | exec="/usr/bin/heketi" 24 | prog="heketi" 25 | user="heketi" 26 | config="/etc/heketi/heketi.json" 27 | pidfile="/var/run/$prog.pid" 28 | lockfile="/var/lock/subsys/$prog" 29 | logfile="/var/log/$prog" 30 | 31 | [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog 32 | 33 | start() { 34 | 35 | [ -x $exec ] || exit 5 36 | [ -f $config ] || exit 6 37 | 38 | if [ ! -f $logfile ] ; then 39 | touch $logfile 40 | chown ${user}:${user} $logfile 41 | chmod 600 $logfile 42 | fi 43 | 44 | if ! [ -f $pidfile ]; then 45 | if [ ! -f $pidfile ] ; then 46 | touch $pidfile 47 | chown ${user}:${user} $pidfile 48 | fi 49 | echo -n "Starting $prog: " 50 | daemon --user heketi "$exec --config=${config} &>> $logfile & echo \$! > $pidfile" 51 | retval=$? 52 | if [ $retval -eq 0 ] ; then 53 | touch $lockfile 54 | echo 55 | fi 56 | return $retval 57 | else 58 | failure 59 | echo 60 | printf "$pidfile still exists...\n" 61 | exit 7 62 | fi 63 | 64 | } 65 | 66 | stop() { 67 | echo -n $"Stopping $prog: " 68 | # stop it here, often "killproc $prog" 69 | killproc -p $pidfile $prog 70 | retval=$? 71 | echo 72 | [ $retval -eq 0 ] && rm -f $lockfile 73 | return $retval 74 | } 75 | 76 | restart() { 77 | stop 78 | start 79 | } 80 | 81 | reload() { 82 | restart 83 | } 84 | 85 | force_reload() { 86 | restart 87 | } 88 | 89 | rh_status() { 90 | # run checks to determine if the service is running or use generic status 91 | status $prog 92 | } 93 | 94 | rh_status_q() { 95 | rh_status >/dev/null 2>&1 96 | } 97 | 98 | 99 | case "$1" in 100 | start) 101 | rh_status_q && exit 0 102 | $1 103 | ;; 104 | stop) 105 | rh_status_q || exit 0 106 | $1 107 | ;; 108 | restart) 109 | $1 110 | ;; 111 | reload) 112 | rh_status_q || exit 7 113 | $1 114 | ;; 115 | force-reload) 116 | force_reload 117 | ;; 118 | status) 119 | rh_status 120 | ;; 121 | condrestart|try-restart) 122 | rh_status_q || exit 0 123 | restart 124 | ;; 125 | *) 126 | echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" 127 | exit 2 128 | esac 129 | exit $? 130 | -------------------------------------------------------------------------------- /tests/functional/TestKubeSmokeTest/testHeketiMock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TOP=../../.. 4 | CURRENT_DIR=`pwd` 5 | FUNCTIONAL_DIR=${CURRENT_DIR}/.. 6 | RESOURCES_DIR=$CURRENT_DIR/resources 7 | PATH=$PATH:$RESOURCES_DIR 8 | 9 | source ${FUNCTIONAL_DIR}/lib.sh 10 | 11 | # Setup Docker environment 12 | eval $(minikube docker-env) 13 | 14 | display_information() { 15 | # Display information 16 | echo -e "\nVersions" 17 | kubectl version 18 | 19 | echo -e "\nDocker containers running" 20 | docker ps 21 | 22 | echo -e "\nDocker images" 23 | docker images 24 | 25 | echo -e "\nShow nodes" 26 | kubectl get nodes 27 | } 28 | 29 | setup_heketi() { 30 | # Start Heketi 31 | echo -e "\nStart Heketi container" 32 | kubectl run heketi --image=heketi/heketi:ci --port=8080 || fail "Unable to start heketi container" 33 | sleep 2 34 | 35 | # This blocks until ready 36 | kubectl expose deployment heketi --type=NodePort || fail "Unable to expose heketi service" 37 | 38 | echo -e "\nShow Topology" 39 | export HEKETI_CLI_SERVER=$(minikube service heketi --url) 40 | heketi-cli topology info 41 | 42 | echo -e "\nLoad mock topology" 43 | heketi-cli topology load --json=mock-topology.json || fail "Unable to load topology" 44 | 45 | echo -e "\nShow Topology" 46 | export HEKETI_CLI_SERVER=$(minikube service heketi --url) 47 | heketi-cli topology info 48 | 49 | echo -e "\nRegister mock endpoints" 50 | kubectl create -f mock-endpoints.json || fail "Unable to submit mock-endpoints" 51 | 52 | echo -e "\nRegister storage class" 53 | sed -e \ 54 | "s#%%URL%%#${HEKETI_CLI_SERVER}#" \ 55 | storageclass.yaml.sed > ${RESOURCES_DIR}/sc.yaml 56 | kubectl create -f ${RESOURCES_DIR}/sc.yaml || fail "Unable to register storage class" 57 | } 58 | 59 | test_create() { 60 | echo "Assert no volumes available" 61 | if heketi-cli volume list | grep Id ; then 62 | heketi-cli volume list 63 | fail "Incorrect number of volumes in Heketi" 64 | fi 65 | 66 | echo "Submit PVC for 100GiB" 67 | kubectl create -f pvc.json || fail "Unable to submit PVC" 68 | 69 | sleep 2 70 | echo "Assert PVC Bound" 71 | if ! kubectl get pvc | grep claim1 | grep Bound ; then 72 | fail "PVC is not Bound" 73 | fi 74 | 75 | echo "Assert only one volume created in Heketi" 76 | if ! heketi-cli volume list | grep Id | wc -l | grep 1 ; then 77 | fail "Incorrect number of volumes in Heketi" 78 | fi 79 | 80 | echo "Assert volume size is 100GiB" 81 | id=`heketi-cli volume list | grep Id | awk '{print $1}' | cut -d: -f2` 82 | if ! heketi-cli volume info ${id} | grep Size | cut -d: -f2 | grep 100 ; then 83 | fail "Invalid size" 84 | fi 85 | } 86 | 87 | test_delete() { 88 | echo "Delete PVC" 89 | kubectl delete pvc claim1 || fail "Unable to delete claim1" 90 | 91 | sleep 30 92 | echo "Assert no volumes available" 93 | if heketi-cli volume list | grep Id ; then 94 | heketi-cli volume list 95 | fail "Incorrect number of volumes in Heketi" 96 | fi 97 | } 98 | 99 | display_information 100 | setup_heketi 101 | 102 | echo -e "\n*** Start tests ***" 103 | test_create 104 | test_delete 105 | 106 | # Ok now start test 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /pkg/kubernetes/backupdb.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2017 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package kubernetes 11 | 12 | import ( 13 | "bytes" 14 | "compress/gzip" 15 | "fmt" 16 | "os" 17 | 18 | "github.com/boltdb/bolt" 19 | 20 | apierrors "k8s.io/apimachinery/pkg/api/errors" 21 | restclient "k8s.io/client-go/rest" 22 | "k8s.io/kubernetes/pkg/api/v1" 23 | clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" 24 | ) 25 | 26 | var ( 27 | inClusterConfig = restclient.InClusterConfig 28 | newForConfig = func(c *restclient.Config) (clientset.Interface, error) { 29 | return clientset.NewForConfig(c) 30 | } 31 | getNamespace = GetNamespace 32 | dbSecretName = "heketi-db-backup" 33 | ) 34 | 35 | func KubeBackupDbToSecret(db *bolt.DB) error { 36 | 37 | // Check if we should use another name for the heketi backup secret 38 | env := os.Getenv("HEKETI_KUBE_DB_SECRET_NAME") 39 | if len(env) != 0 { 40 | dbSecretName = env 41 | } 42 | 43 | // Get Kubernetes configuration 44 | kubeConfig, err := inClusterConfig() 45 | if err != nil { 46 | return fmt.Errorf("Unable to get kubernetes configuration: %v", err) 47 | } 48 | 49 | // Get clientset 50 | c, err := newForConfig(kubeConfig) 51 | if err != nil { 52 | return fmt.Errorf("Unable to get kubernetes clientset: %v", err) 53 | } 54 | 55 | // Get namespace 56 | ns, err := getNamespace() 57 | if err != nil { 58 | return fmt.Errorf("Unable to get namespace: %v", err) 59 | } 60 | 61 | // Create client for secrets 62 | secrets := c.CoreV1().Secrets(ns) 63 | if err != nil { 64 | return fmt.Errorf("Unable to get a client to kubernetes secrets: %v", err) 65 | } 66 | 67 | // Get a backup 68 | err = db.View(func(tx *bolt.Tx) error { 69 | var backup bytes.Buffer 70 | 71 | gz := gzip.NewWriter(&backup) 72 | _, err := tx.WriteTo(gz) 73 | if err != nil { 74 | return fmt.Errorf("Unable to access database: %v", err) 75 | } 76 | if err := gz.Close(); err != nil { 77 | return fmt.Errorf("Unable to close gzipped database: %v", err) 78 | } 79 | 80 | // Create a secret with backup 81 | secret := &v1.Secret{} 82 | secret.Kind = "Secret" 83 | secret.Namespace = ns 84 | secret.APIVersion = "v1" 85 | secret.ObjectMeta.Name = dbSecretName 86 | secret.Data = map[string][]byte{ 87 | "heketi.db.gz": backup.Bytes(), 88 | } 89 | 90 | // Submit secret 91 | _, err = secrets.Create(secret) 92 | if apierrors.IsAlreadyExists(err) { 93 | // It already exists, so just update it instead 94 | _, err = secrets.Update(secret) 95 | if err != nil { 96 | return fmt.Errorf("Unable to update database to secret: %v", err) 97 | } 98 | } else if err != nil { 99 | return fmt.Errorf("Unable to create database secret: %v", err) 100 | } 101 | 102 | return nil 103 | 104 | }) 105 | if err != nil { 106 | return fmt.Errorf("Unable to backup database to kubernetes secret: %v", err) 107 | } 108 | 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /executors/kubeexec/kubeexec_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package kubeexec 11 | 12 | import ( 13 | "os" 14 | "testing" 15 | 16 | restclient "k8s.io/client-go/rest" 17 | 18 | "github.com/heketi/heketi/executors/sshexec" 19 | "github.com/heketi/heketi/pkg/utils" 20 | "github.com/heketi/tests" 21 | ) 22 | 23 | func init() { 24 | inClusterConfig = func() (*restclient.Config, error) { 25 | return &restclient.Config{}, nil 26 | } 27 | logger.SetLevel(utils.LEVEL_NOLOG) 28 | } 29 | 30 | func TestNewKubeExecutor(t *testing.T) { 31 | config := &KubeConfig{ 32 | CLICommandConfig: sshexec.CLICommandConfig{ 33 | Fstab: "myfstab", 34 | }, 35 | Namespace: "mynamespace", 36 | } 37 | 38 | k, err := NewKubeExecutor(config) 39 | tests.Assert(t, err == nil) 40 | tests.Assert(t, k.Fstab == "myfstab") 41 | tests.Assert(t, k.Throttlemap != nil) 42 | tests.Assert(t, k.config != nil) 43 | } 44 | 45 | func TestNewKubeExecutorNoNamespace(t *testing.T) { 46 | config := &KubeConfig{ 47 | CLICommandConfig: sshexec.CLICommandConfig{ 48 | Fstab: "myfstab", 49 | }, 50 | } 51 | 52 | k, err := NewKubeExecutor(config) 53 | tests.Assert(t, err != nil) 54 | tests.Assert(t, k == nil) 55 | } 56 | 57 | func TestNewKubeExecutorRebalanceOnExpansion(t *testing.T) { 58 | 59 | // This tests access to configurations 60 | // from the sshconfig exector 61 | 62 | config := &KubeConfig{ 63 | CLICommandConfig: sshexec.CLICommandConfig{ 64 | Fstab: "myfstab", 65 | }, 66 | Namespace: "mynamespace", 67 | } 68 | 69 | k, err := NewKubeExecutor(config) 70 | tests.Assert(t, err == nil) 71 | tests.Assert(t, k.Fstab == "myfstab") 72 | tests.Assert(t, k.Throttlemap != nil) 73 | tests.Assert(t, k.config != nil) 74 | tests.Assert(t, k.RebalanceOnExpansion() == false) 75 | 76 | config = &KubeConfig{ 77 | CLICommandConfig: sshexec.CLICommandConfig{ 78 | Fstab: "myfstab", 79 | RebalanceOnExpansion: true, 80 | }, 81 | Namespace: "mynamespace", 82 | } 83 | 84 | k, err = NewKubeExecutor(config) 85 | tests.Assert(t, err == nil) 86 | tests.Assert(t, k.Fstab == "myfstab") 87 | tests.Assert(t, k.Throttlemap != nil) 88 | tests.Assert(t, k.config != nil) 89 | tests.Assert(t, k.RebalanceOnExpansion() == true) 90 | } 91 | 92 | func TestKubeExecutorEnvVariables(t *testing.T) { 93 | 94 | // set environment 95 | err := os.Setenv("HEKETI_SNAPSHOT_LIMIT", "999") 96 | tests.Assert(t, err == nil) 97 | defer os.Unsetenv("HEKETI_SNAPSHOT_LIMIT") 98 | 99 | err = os.Setenv("HEKETI_FSTAB", "anotherfstab") 100 | tests.Assert(t, err == nil) 101 | defer os.Unsetenv("HEKETI_FSTAB") 102 | 103 | config := &KubeConfig{ 104 | CLICommandConfig: sshexec.CLICommandConfig{ 105 | Fstab: "myfstab", 106 | }, 107 | Namespace: "mynamespace", 108 | } 109 | 110 | k, err := NewKubeExecutor(config) 111 | tests.Assert(t, err == nil) 112 | tests.Assert(t, k.Throttlemap != nil) 113 | tests.Assert(t, k.config != nil) 114 | tests.Assert(t, k.Fstab == "anotherfstab") 115 | tests.Assert(t, k.SnapShotLimit() == 999) 116 | 117 | } 118 | -------------------------------------------------------------------------------- /apps/glusterfs/dbentry.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/boltdb/bolt" 14 | "github.com/lpabon/godbc" 15 | ) 16 | 17 | type DbEntry interface { 18 | BucketName() string 19 | Marshal() ([]byte, error) 20 | Unmarshal(buffer []byte) error 21 | } 22 | 23 | // Checks if the key already exists in the database. If it does not exist, 24 | // then it will save the key value pair in the database bucket. 25 | func EntryRegister(tx *bolt.Tx, entry DbEntry, key string, value []byte) ([]byte, error) { 26 | godbc.Require(tx != nil) 27 | godbc.Require(len(key) > 0) 28 | 29 | // Access bucket 30 | b := tx.Bucket([]byte(entry.BucketName())) 31 | if b == nil { 32 | err := ErrDbAccess 33 | logger.Err(err) 34 | return nil, err 35 | } 36 | 37 | // Check if key exists already 38 | val := b.Get([]byte(key)) 39 | if val != nil { 40 | return val, ErrKeyExists 41 | } 42 | 43 | // Key does not exist. We can save it 44 | err := b.Put([]byte(key), value) 45 | if err != nil { 46 | logger.Err(err) 47 | return nil, err 48 | } 49 | 50 | return nil, nil 51 | } 52 | 53 | func EntryKeys(tx *bolt.Tx, bucket string) []string { 54 | list := make([]string, 0) 55 | 56 | // Get all the cluster ids from the DB 57 | b := tx.Bucket([]byte(bucket)) 58 | if b == nil { 59 | return nil 60 | } 61 | 62 | err := b.ForEach(func(k, v []byte) error { 63 | list = append(list, string(k)) 64 | return nil 65 | }) 66 | if err != nil { 67 | return nil 68 | } 69 | 70 | return list 71 | } 72 | 73 | func EntrySave(tx *bolt.Tx, entry DbEntry, key string) error { 74 | godbc.Require(tx != nil) 75 | godbc.Require(len(key) > 0) 76 | 77 | // Access bucket 78 | b := tx.Bucket([]byte(entry.BucketName())) 79 | if b == nil { 80 | err := ErrDbAccess 81 | logger.Err(err) 82 | return err 83 | } 84 | 85 | // Save device entry to db 86 | buffer, err := entry.Marshal() 87 | if err != nil { 88 | logger.Err(err) 89 | return err 90 | } 91 | 92 | // Save data using the id as the key 93 | err = b.Put([]byte(key), buffer) 94 | if err != nil { 95 | logger.Err(err) 96 | return err 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func EntryDelete(tx *bolt.Tx, entry DbEntry, key string) error { 103 | godbc.Require(tx != nil) 104 | godbc.Require(len(key) > 0) 105 | 106 | // Access bucket 107 | b := tx.Bucket([]byte(entry.BucketName())) 108 | if b == nil { 109 | err := ErrDbAccess 110 | logger.Err(err) 111 | return err 112 | } 113 | 114 | // Delete key 115 | err := b.Delete([]byte(key)) 116 | if err != nil { 117 | logger.LogError("Unable to delete key [%v] in db: %v", key, err.Error()) 118 | return err 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func EntryLoad(tx *bolt.Tx, entry DbEntry, key string) error { 125 | godbc.Require(tx != nil) 126 | godbc.Require(len(key) > 0) 127 | 128 | b := tx.Bucket([]byte(entry.BucketName())) 129 | if b == nil { 130 | err := ErrDbAccess 131 | logger.Err(err) 132 | return err 133 | } 134 | 135 | val := b.Get([]byte(key)) 136 | if val == nil { 137 | return ErrNotFound 138 | } 139 | 140 | err := entry.Unmarshal(val) 141 | if err != nil { 142 | logger.Err(err) 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | -------------------------------------------------------------------------------- /client/cli/go/topology-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "clusters": [ 3 | { 4 | "nodes": [ 5 | { 6 | "node": { 7 | "hostnames": { 8 | "manage": [ 9 | "192.168.10.100" 10 | ], 11 | "storage": [ 12 | "192.168.10.100" 13 | ] 14 | }, 15 | "zone": 1 16 | }, 17 | "devices": [ 18 | "/dev/sdb", 19 | "/dev/sdc", 20 | "/dev/sdd", 21 | "/dev/sde", 22 | "/dev/sdf", 23 | "/dev/sdg", 24 | "/dev/sdh", 25 | "/dev/sdi" 26 | ] 27 | }, 28 | { 29 | "node": { 30 | "hostnames": { 31 | "manage": [ 32 | "192.168.10.101" 33 | ], 34 | "storage": [ 35 | "192.168.10.101" 36 | ] 37 | }, 38 | "zone": 2 39 | }, 40 | "devices": [ 41 | "/dev/sdb", 42 | "/dev/sdc", 43 | "/dev/sdd", 44 | "/dev/sde", 45 | "/dev/sdf", 46 | "/dev/sdg", 47 | "/dev/sdh", 48 | "/dev/sdi" 49 | ] 50 | }, 51 | { 52 | "node": { 53 | "hostnames": { 54 | "manage": [ 55 | "192.168.10.102" 56 | ], 57 | "storage": [ 58 | "192.168.10.102" 59 | ] 60 | }, 61 | "zone": 1 62 | }, 63 | "devices": [ 64 | "/dev/sdb", 65 | "/dev/sdc", 66 | "/dev/sdd", 67 | "/dev/sde", 68 | "/dev/sdf", 69 | "/dev/sdg", 70 | "/dev/sdh", 71 | "/dev/sdi" 72 | ] 73 | }, 74 | { 75 | "node": { 76 | "hostnames": { 77 | "manage": [ 78 | "192.168.10.103" 79 | ], 80 | "storage": [ 81 | "192.168.10.103" 82 | ] 83 | }, 84 | "zone": 2 85 | }, 86 | "devices": [ 87 | "/dev/sdb", 88 | "/dev/sdc", 89 | "/dev/sdd", 90 | "/dev/sde", 91 | "/dev/sdf", 92 | "/dev/sdg", 93 | "/dev/sdh", 94 | "/dev/sdi" 95 | ] 96 | } 97 | ] 98 | } 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /client/api/go-client/cluster.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), as published by the Free Software Foundation, 7 | // or under the Apache License, Version 2.0 . 9 | // 10 | // You may not use this file except in compliance with those terms. 11 | // 12 | 13 | package client 14 | 15 | import ( 16 | "bytes" 17 | "net/http" 18 | 19 | "github.com/heketi/heketi/pkg/glusterfs/api" 20 | "github.com/heketi/heketi/pkg/utils" 21 | ) 22 | 23 | func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) { 24 | 25 | // Create a request 26 | req, err := http.NewRequest("POST", c.host+"/clusters", bytes.NewBuffer([]byte(`{}`))) 27 | if err != nil { 28 | return nil, err 29 | } 30 | req.Header.Set("Content-Type", "application/json") 31 | 32 | // Set token 33 | err = c.setToken(req) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | // Send request 39 | r, err := c.do(req) 40 | if err != nil { 41 | return nil, err 42 | } 43 | if r.StatusCode != http.StatusCreated { 44 | return nil, utils.GetErrorFromResponse(r) 45 | } 46 | 47 | // Read JSON response 48 | var cluster api.ClusterInfoResponse 49 | err = utils.GetJsonFromResponse(r, &cluster) 50 | r.Body.Close() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return &cluster, nil 56 | } 57 | 58 | func (c *Client) ClusterInfo(id string) (*api.ClusterInfoResponse, error) { 59 | 60 | // Create request 61 | req, err := http.NewRequest("GET", c.host+"/clusters/"+id, nil) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | // Set token 67 | err = c.setToken(req) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | // Get info 73 | r, err := c.do(req) 74 | if err != nil { 75 | return nil, err 76 | } 77 | if r.StatusCode != http.StatusOK { 78 | return nil, utils.GetErrorFromResponse(r) 79 | } 80 | 81 | // Read JSON response 82 | var cluster api.ClusterInfoResponse 83 | err = utils.GetJsonFromResponse(r, &cluster) 84 | r.Body.Close() 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | return &cluster, nil 90 | } 91 | 92 | func (c *Client) ClusterList() (*api.ClusterListResponse, error) { 93 | 94 | // Create request 95 | req, err := http.NewRequest("GET", c.host+"/clusters", nil) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | // Set token 101 | err = c.setToken(req) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | // Get info 107 | r, err := c.do(req) 108 | if err != nil { 109 | return nil, err 110 | } 111 | if r.StatusCode != http.StatusOK { 112 | return nil, utils.GetErrorFromResponse(r) 113 | } 114 | 115 | // Read JSON response 116 | var clusters api.ClusterListResponse 117 | err = utils.GetJsonFromResponse(r, &clusters) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return &clusters, nil 123 | } 124 | 125 | func (c *Client) ClusterDelete(id string) error { 126 | 127 | // Create DELETE request 128 | req, err := http.NewRequest("DELETE", c.host+"/clusters/"+id, nil) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | // Set token 134 | err = c.setToken(req) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | // Send request 140 | r, err := c.do(req) 141 | if err != nil { 142 | return err 143 | } 144 | if r.StatusCode != http.StatusOK { 145 | return utils.GetErrorFromResponse(r) 146 | } 147 | 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /pkg/heketitest/heketitest.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package heketitest 11 | 12 | import ( 13 | "bytes" 14 | "net/http/httptest" 15 | "os" 16 | 17 | "github.com/gorilla/mux" 18 | "github.com/heketi/heketi/apps/glusterfs" 19 | "github.com/heketi/heketi/middleware" 20 | "github.com/heketi/tests" 21 | "github.com/lpabon/godbc" 22 | "github.com/urfave/negroni" 23 | ) 24 | 25 | // Heketi test server configuration 26 | type HeketiMockTestServerConfig struct { 27 | Auth bool 28 | AdminKey string 29 | UserKey string 30 | Logging bool 31 | } 32 | 33 | // Heketi test service metadata 34 | type HeketiMockTestServer struct { 35 | DbFile string 36 | Ts *httptest.Server 37 | App *glusterfs.App 38 | } 39 | 40 | // Create a simple Heketi mock server 41 | // 42 | // Example: 43 | // h := heketitest.NewHeketiMockTestServerDefault() 44 | // defer h.Close() 45 | // 46 | func NewHeketiMockTestServerDefault() *HeketiMockTestServer { 47 | return NewHeketiMockTestServer(nil) 48 | } 49 | 50 | // Create a Heketi mock server 51 | // 52 | // Example: 53 | // c := &heketitest.HeketiMockTestServerConfig{ 54 | // Auth: true, 55 | // AdminKey: "admin", 56 | // UserKey: "user", 57 | // Logging: false, 58 | // } 59 | // 60 | // h := heketitest.NewHeketiMockTestServer(c) 61 | // defer h.Close() 62 | // 63 | func NewHeketiMockTestServer( 64 | config *HeketiMockTestServerConfig) *HeketiMockTestServer { 65 | 66 | if config == nil { 67 | config = &HeketiMockTestServerConfig{} 68 | } 69 | 70 | h := &HeketiMockTestServer{} 71 | h.DbFile = tests.Tempfile() 72 | 73 | // Set loglevel 74 | var loglevel string 75 | if config.Logging { 76 | loglevel = "debug" 77 | } else { 78 | loglevel = "none" 79 | } 80 | 81 | // Create simple configuration for unit tests 82 | appConfig := bytes.NewBuffer([]byte(`{ 83 | "glusterfs" : { 84 | "executor" : "mock", 85 | "allocator" : "simple", 86 | "loglevel" : "` + loglevel + `", 87 | "db" : "` + h.DbFile + `" 88 | } 89 | }`)) 90 | h.App = glusterfs.NewApp(appConfig) 91 | if h.App == nil { 92 | return nil 93 | } 94 | 95 | // Initialize REST service 96 | h.Ts = h.setupHeketiServer(config) 97 | if h.Ts == nil { 98 | return nil 99 | } 100 | 101 | return h 102 | } 103 | 104 | // Get http test service struct 105 | func (h *HeketiMockTestServer) HttpServer() *httptest.Server { 106 | return h.Ts 107 | } 108 | 109 | // Get URL to test server 110 | func (h *HeketiMockTestServer) URL() string { 111 | return h.Ts.URL 112 | } 113 | 114 | // Close database and other services 115 | func (h *HeketiMockTestServer) Close() { 116 | os.Remove(h.DbFile) 117 | h.App.Close() 118 | h.Ts.Close() 119 | } 120 | 121 | func (h *HeketiMockTestServer) setupHeketiServer( 122 | config *HeketiMockTestServerConfig) *httptest.Server { 123 | 124 | godbc.Require(h.App != nil) 125 | 126 | router := mux.NewRouter() 127 | h.App.SetRoutes(router) 128 | n := negroni.New() 129 | 130 | // Add authentication 131 | if config.Auth { 132 | jwtconfig := &middleware.JwtAuthConfig{} 133 | jwtconfig.Admin.PrivateKey = config.AdminKey 134 | jwtconfig.User.PrivateKey = config.UserKey 135 | 136 | // Setup middleware 137 | n.Use(middleware.NewJwtAuth(jwtconfig)) 138 | n.UseFunc(h.App.Auth) 139 | } 140 | 141 | // Add App 142 | n.UseHandler(router) 143 | 144 | // Create server 145 | return httptest.NewServer(n) 146 | } 147 | -------------------------------------------------------------------------------- /middleware/jwt.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package middleware 11 | 12 | import ( 13 | "crypto/sha256" 14 | "encoding/hex" 15 | "errors" 16 | "fmt" 17 | "github.com/auth0/go-jwt-middleware" 18 | jwt "github.com/dgrijalva/jwt-go" 19 | "github.com/gorilla/context" 20 | "net/http" 21 | ) 22 | 23 | var ( 24 | required_claims = []string{"iss", "iat", "exp"} 25 | ) 26 | 27 | type JwtAuth struct { 28 | adminKey []byte 29 | userKey []byte 30 | } 31 | 32 | type Issuer struct { 33 | PrivateKey string `json:"key"` 34 | } 35 | 36 | type JwtAuthConfig struct { 37 | Admin Issuer `json:"admin"` 38 | User Issuer `json:"user"` 39 | } 40 | 41 | func generate_qsh(r *http.Request) string { 42 | // Please see Heketi REST API for more information 43 | claim := r.Method + "&" + r.URL.Path 44 | hash := sha256.New() 45 | hash.Write([]byte(claim)) 46 | return hex.EncodeToString(hash.Sum(nil)) 47 | } 48 | 49 | func NewJwtAuth(config *JwtAuthConfig) *JwtAuth { 50 | 51 | if config.Admin.PrivateKey == "" || 52 | config.User.PrivateKey == "" { 53 | return nil 54 | } 55 | 56 | j := &JwtAuth{} 57 | j.adminKey = []byte(config.Admin.PrivateKey) 58 | j.userKey = []byte(config.User.PrivateKey) 59 | 60 | return j 61 | } 62 | 63 | func (j *JwtAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 64 | 65 | // Access token from header 66 | rawtoken, err := jwtmiddleware.FromAuthHeader(r) 67 | if err != nil { 68 | http.Error(w, err.Error(), http.StatusBadRequest) 69 | return 70 | } 71 | 72 | // Determine if we have the token 73 | if rawtoken == "" { 74 | http.Error(w, "Required authorization token not found", http.StatusUnauthorized) 75 | return 76 | } 77 | 78 | // Parse token 79 | var claims jwt.MapClaims 80 | token, err := jwt.Parse(rawtoken, func(token *jwt.Token) (interface{}, error) { 81 | 82 | // Verify Method 83 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 84 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 85 | } 86 | 87 | claims = token.Claims.(jwt.MapClaims) 88 | if claims == nil { 89 | return nil, fmt.Errorf("No claims found in token") 90 | } 91 | 92 | // Get claims 93 | if issuer, ok := claims["iss"]; ok { 94 | switch issuer { 95 | case "admin": 96 | return j.adminKey, nil 97 | case "user": 98 | return j.userKey, nil 99 | default: 100 | return nil, errors.New("Unknown user") 101 | } 102 | } 103 | 104 | return nil, errors.New("Token missing iss claim") 105 | }) 106 | if err != nil { 107 | http.Error(w, err.Error(), http.StatusUnauthorized) 108 | return 109 | } 110 | 111 | if !token.Valid { 112 | http.Error(w, "Invalid token", http.StatusUnauthorized) 113 | return 114 | } 115 | 116 | // Check for required claims 117 | for _, required_claim := range required_claims { 118 | if _, ok := claims[required_claim]; !ok { 119 | // Claim missing 120 | http.Error(w, fmt.Sprintf("Required claim %v missing from token", required_claim), http.StatusBadRequest) 121 | return 122 | } 123 | } 124 | 125 | // Check qsh claim 126 | if claims["qsh"] != generate_qsh(r) { 127 | http.Error(w, "Invalid qsh claim in token", http.StatusUnauthorized) 128 | return 129 | } 130 | 131 | // Store token in request for other middleware to access 132 | context.Set(r, "jwt", token) 133 | 134 | // Everything passes call next middleware 135 | next(w, r) 136 | } 137 | -------------------------------------------------------------------------------- /executors/sshexec/peer_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package sshexec 11 | 12 | import ( 13 | "testing" 14 | 15 | "github.com/heketi/heketi/pkg/utils" 16 | "github.com/heketi/tests" 17 | ) 18 | 19 | func TestSshExecPeerProbe(t *testing.T) { 20 | 21 | f := NewFakeSsh() 22 | defer tests.Patch(&sshNew, 23 | func(logger *utils.Logger, user string, file string) (Ssher, error) { 24 | return f, nil 25 | }).Restore() 26 | 27 | config := &SshConfig{ 28 | PrivateKeyFile: "xkeyfile", 29 | User: "xuser", 30 | CLICommandConfig: CLICommandConfig{ 31 | Fstab: "/my/fstab", 32 | }, 33 | } 34 | 35 | s, err := NewSshExecutor(config) 36 | tests.Assert(t, err == nil) 37 | tests.Assert(t, s != nil) 38 | 39 | // Mock ssh function 40 | f.FakeConnectAndExec = func(host string, 41 | commands []string, 42 | timeoutMinutes int, 43 | useSudo bool) ([]string, error) { 44 | 45 | tests.Assert(t, host == "host:22", host) 46 | tests.Assert(t, len(commands) == 1) 47 | tests.Assert(t, commands[0] == "gluster peer probe newnode", commands) 48 | 49 | return nil, nil 50 | } 51 | 52 | // Call function 53 | err = s.PeerProbe("host", "newnode") 54 | tests.Assert(t, err == nil, err) 55 | 56 | // Now set the snapshot limit 57 | config = &SshConfig{ 58 | PrivateKeyFile: "xkeyfile", 59 | User: "xuser", 60 | CLICommandConfig: CLICommandConfig{ 61 | Fstab: "/my/fstab", 62 | SnapShotLimit: 14, 63 | }, 64 | } 65 | 66 | s, err = NewSshExecutor(config) 67 | tests.Assert(t, err == nil) 68 | tests.Assert(t, s != nil) 69 | 70 | // Mock ssh function 71 | count := 0 72 | f.FakeConnectAndExec = func(host string, 73 | commands []string, 74 | timeoutMinutes int, 75 | useSudo bool) ([]string, error) { 76 | 77 | switch count { 78 | case 0: 79 | tests.Assert(t, host == "host:22", host) 80 | tests.Assert(t, len(commands) == 1) 81 | tests.Assert(t, commands[0] == "gluster peer probe newnode", commands) 82 | 83 | case 1: 84 | tests.Assert(t, host == "host:22", host) 85 | tests.Assert(t, len(commands) == 1) 86 | tests.Assert(t, commands[0] == "gluster --mode=script snapshot config snap-max-hard-limit 14", commands) 87 | 88 | default: 89 | tests.Assert(t, false, "Should not be reached") 90 | } 91 | count++ 92 | 93 | return nil, nil 94 | } 95 | 96 | // Call function 97 | err = s.PeerProbe("host", "newnode") 98 | tests.Assert(t, err == nil, err) 99 | tests.Assert(t, count == 2) 100 | 101 | } 102 | 103 | func TestSshExecGlusterdCheck(t *testing.T) { 104 | f := NewFakeSsh() 105 | defer tests.Patch(&sshNew, 106 | func(logger *utils.Logger, user string, file string) (Ssher, error) { 107 | return f, nil 108 | }).Restore() 109 | 110 | config := &SshConfig{ 111 | PrivateKeyFile: "xkeyfile", 112 | User: "xuser", 113 | CLICommandConfig: CLICommandConfig{ 114 | Fstab: "/my/fstab", 115 | }, 116 | } 117 | 118 | s, err := NewSshExecutor(config) 119 | tests.Assert(t, err == nil) 120 | tests.Assert(t, s != nil) 121 | 122 | // Mock ssh function 123 | f.FakeConnectAndExec = func(host string, 124 | commands []string, 125 | timeoutMinutes int, 126 | useSudo bool) ([]string, error) { 127 | 128 | tests.Assert(t, host == "newhost:22", host) 129 | tests.Assert(t, len(commands) == 1) 130 | tests.Assert(t, commands[0] == "systemctl status glusterd", commands) 131 | 132 | return nil, nil 133 | } 134 | 135 | // Call function 136 | err = s.GlusterdCheck("newhost") 137 | tests.Assert(t, err == nil, err) 138 | } 139 | -------------------------------------------------------------------------------- /executors/sshexec/device.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package sshexec 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "github.com/heketi/heketi/executors" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | const ( 21 | VGDISPLAY_SIZE_KB = 11 22 | VGDISPLAY_PHYSICAL_EXTENT_SIZE = 12 23 | VGDISPLAY_TOTAL_NUMBER_EXTENTS = 13 24 | VGDISPLAY_ALLOCATED_NUMBER_EXTENTS = 14 25 | VGDISPLAY_FREE_NUMBER_EXTENTS = 15 26 | ) 27 | 28 | // Read: 29 | // https://access.redhat.com/documentation/en-US/Red_Hat_Storage/3.1/html/Administration_Guide/Brick_Configuration.html 30 | // 31 | 32 | func (s *SshExecutor) DeviceSetup(host, device, vgid string) (d *executors.DeviceInfo, e error) { 33 | 34 | // Setup commands 35 | commands := []string{ 36 | fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K %v", device), 37 | fmt.Sprintf("vgcreate %v %v", s.vgName(vgid), device), 38 | } 39 | 40 | // Execute command 41 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | // Create a cleanup function if anything fails 47 | defer func() { 48 | if e != nil { 49 | s.DeviceTeardown(host, device, vgid) 50 | } 51 | }() 52 | 53 | // Vg info 54 | d = &executors.DeviceInfo{} 55 | err = s.getVgSizeFromNode(d, host, device, vgid) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return d, nil 61 | } 62 | 63 | func (s *SshExecutor) DeviceTeardown(host, device, vgid string) error { 64 | 65 | // Setup commands 66 | commands := []string{ 67 | fmt.Sprintf("vgremove %v", s.vgName(vgid)), 68 | fmt.Sprintf("pvremove %v", device), 69 | } 70 | 71 | // Execute command 72 | _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) 73 | if err != nil { 74 | logger.LogError("Error while deleting device %v on %v with id %v", 75 | device, host, vgid) 76 | } 77 | 78 | commands = []string{ 79 | fmt.Sprintf("ls %v/%v", rootMountPoint, s.vgName(vgid)), 80 | } 81 | _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) 82 | if err != nil { 83 | return nil 84 | } 85 | 86 | commands = []string{ 87 | fmt.Sprintf("rmdir %v/%v", rootMountPoint, s.vgName(vgid)), 88 | } 89 | 90 | _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) 91 | if err != nil { 92 | logger.LogError("Error while removing the VG directory") 93 | return nil 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func (s *SshExecutor) getVgSizeFromNode( 100 | d *executors.DeviceInfo, 101 | host, device, vgid string) error { 102 | 103 | // Setup command 104 | commands := []string{ 105 | fmt.Sprintf("vgdisplay -c %v", s.vgName(vgid)), 106 | } 107 | 108 | // Execute command 109 | b, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | // Example: 115 | // sampleVg:r/w:772:-1:0:0:0:-1:0:4:4:2097135616:4096:511996:0:511996:rJ0bIG-3XNc-NoS0-fkKm-batK-dFyX-xbxHym 116 | vginfo := strings.Split(b[0], ":") 117 | 118 | // See vgdisplay manpage 119 | if len(vginfo) < 17 { 120 | return errors.New("vgdisplay returned an invalid string") 121 | } 122 | 123 | extent_size, err := 124 | strconv.ParseUint(vginfo[VGDISPLAY_PHYSICAL_EXTENT_SIZE], 10, 64) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | free_extents, err := 130 | strconv.ParseUint(vginfo[VGDISPLAY_FREE_NUMBER_EXTENTS], 10, 64) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | d.Size = free_extents * extent_size 136 | d.ExtentSize = extent_size 137 | logger.Debug("Size of %v in %v is %v", device, host, d.Size) 138 | return nil 139 | } 140 | -------------------------------------------------------------------------------- /apps/glusterfs/allocator_mock.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "github.com/boltdb/bolt" 14 | "github.com/heketi/heketi/pkg/utils" 15 | "sort" 16 | "sync" 17 | ) 18 | 19 | type MockAllocator struct { 20 | clustermap map[string]sort.StringSlice 21 | lock sync.Mutex 22 | db bolt.DB 23 | } 24 | 25 | func NewMockAllocator(db *bolt.DB) *MockAllocator { 26 | d := &MockAllocator{} 27 | d.clustermap = make(map[string]sort.StringSlice) 28 | 29 | var clusters []string 30 | err := db.View(func(tx *bolt.Tx) error { 31 | var err error 32 | clusters, err = ClusterList(tx) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | for _, cluster := range clusters { 38 | err := d.addDevicesFromDb(tx, cluster) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | 44 | return nil 45 | }) 46 | if err != nil { 47 | return nil 48 | } 49 | 50 | return d 51 | } 52 | 53 | func (d *MockAllocator) AddDevice(cluster *ClusterEntry, 54 | node *NodeEntry, 55 | device *DeviceEntry) error { 56 | 57 | d.lock.Lock() 58 | defer d.lock.Unlock() 59 | 60 | clusterId := cluster.Info.Id 61 | deviceId := device.Info.Id 62 | 63 | if devicelist, ok := d.clustermap[clusterId]; ok { 64 | devicelist = append(devicelist, deviceId) 65 | devicelist.Sort() 66 | d.clustermap[clusterId] = devicelist 67 | } else { 68 | d.clustermap[clusterId] = sort.StringSlice{deviceId} 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (d *MockAllocator) RemoveDevice(cluster *ClusterEntry, 75 | node *NodeEntry, 76 | device *DeviceEntry) error { 77 | 78 | d.lock.Lock() 79 | defer d.lock.Unlock() 80 | 81 | clusterId := cluster.Info.Id 82 | deviceId := device.Info.Id 83 | 84 | d.clustermap[clusterId] = utils.SortedStringsDelete(d.clustermap[clusterId], deviceId) 85 | 86 | return nil 87 | } 88 | 89 | func (d *MockAllocator) RemoveCluster(clusterId string) error { 90 | // Save in the object 91 | d.lock.Lock() 92 | defer d.lock.Unlock() 93 | 94 | delete(d.clustermap, clusterId) 95 | 96 | return nil 97 | } 98 | 99 | func (d *MockAllocator) GetNodes(clusterId, brickId string) (<-chan string, 100 | chan<- struct{}, <-chan error) { 101 | 102 | // Initialize channels 103 | device, done := make(chan string), make(chan struct{}) 104 | 105 | // Make sure to make a buffered channel for the error, so we can 106 | // set it and return 107 | errc := make(chan error, 1) 108 | 109 | d.lock.Lock() 110 | devicelist := d.clustermap[clusterId] 111 | d.lock.Unlock() 112 | 113 | // Start generator in a new goroutine 114 | go func() { 115 | defer func() { 116 | errc <- nil 117 | close(device) 118 | }() 119 | 120 | for _, id := range devicelist { 121 | select { 122 | case device <- id: 123 | case <-done: 124 | return 125 | } 126 | } 127 | 128 | }() 129 | 130 | return device, done, errc 131 | } 132 | 133 | func (d *MockAllocator) addDevicesFromDb(tx *bolt.Tx, clusterId string) error { 134 | // Get data from the DB 135 | devicelist := make(sort.StringSlice, 0) 136 | 137 | // Get cluster info 138 | cluster, err := NewClusterEntryFromId(tx, clusterId) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | for _, nodeId := range cluster.Info.Nodes { 144 | node, err := NewNodeEntryFromId(tx, nodeId) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | devicelist = append(devicelist, node.Devices...) 150 | } 151 | 152 | // We have to sort the list so that later we can search and delete an entry 153 | devicelist.Sort() 154 | 155 | // Save in the object 156 | d.lock.Lock() 157 | defer d.lock.Unlock() 158 | 159 | d.clustermap[clusterId] = devicelist 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Based on http://chrismckenzie.io/post/deploying-with-golang/ 3 | # 4 | 5 | .PHONY: version all run dist clean 6 | 7 | APP_NAME := heketi 8 | CLIENT_PKG_NAME := heketi-client 9 | SHA := $(shell git rev-parse --short HEAD) 10 | BRANCH := $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 11 | VER := $(shell git describe) 12 | ARCH := $(shell go env GOARCH) 13 | GOOS := $(shell go env GOOS) 14 | GLIDEPATH := $(shell command -v glide 2> /dev/null) 15 | DIR=. 16 | 17 | ifdef APP_SUFFIX 18 | VERSION = $(VER)-$(subst /,-,$(APP_SUFFIX)) 19 | else 20 | ifeq (master,$(BRANCH)) 21 | VERSION = $(VER) 22 | else 23 | VERSION = $(VER)-$(BRANCH) 24 | endif 25 | endif 26 | 27 | # Go setup 28 | GO=go 29 | 30 | # Sources and Targets 31 | EXECUTABLES :=$(APP_NAME) 32 | # Build Binaries setting main.version and main.build vars 33 | LDFLAGS :=-ldflags "-X main.HEKETI_VERSION=$(VERSION) -extldflags '-z relro -z now'" 34 | # Package target 35 | PACKAGE :=$(DIR)/dist/$(APP_NAME)-$(VERSION).$(GOOS).$(ARCH).tar.gz 36 | CLIENT_PACKAGE :=$(DIR)/dist/$(APP_NAME)-client-$(VERSION).$(GOOS).$(ARCH).tar.gz 37 | GOFILES=$(shell go list ./... | grep -v vendor) 38 | 39 | .DEFAULT: all 40 | 41 | all: server client 42 | 43 | # print the version 44 | version: 45 | @echo $(VERSION) 46 | 47 | # print the name of the app 48 | name: 49 | @echo $(APP_NAME) 50 | 51 | # print the package path 52 | package: 53 | @echo $(PACKAGE) 54 | 55 | heketi: glide.lock vendor 56 | go build $(LDFLAGS) -o $(APP_NAME) 57 | 58 | server: heketi 59 | 60 | vendor: 61 | ifndef GLIDEPATH 62 | $(info Please install glide.) 63 | $(info Install it using your package manager or) 64 | $(info by running: curl https://glide.sh/get | sh.) 65 | $(info ) 66 | $(error glide is required to continue) 67 | endif 68 | echo "Installing vendor directory" 69 | glide install -v 70 | 71 | echo "Building dependencies to make builds faster" 72 | go install github.com/heketi/heketi 73 | 74 | glide.lock: glide.yaml 75 | echo "Glide.yaml has changed, updating glide.lock" 76 | glide update -v 77 | 78 | client: glide.lock vendor 79 | @$(MAKE) -C client/cli/go 80 | 81 | run: server 82 | ./$(APP_NAME) 83 | 84 | test: glide.lock vendor 85 | go test $(GOFILES) 86 | 87 | clean: 88 | @echo Cleaning Workspace... 89 | rm -rf $(APP_NAME) 90 | rm -rf dist 91 | @$(MAKE) -C client/cli/go clean 92 | 93 | $(PACKAGE): all 94 | @echo Packaging Binaries... 95 | @mkdir -p tmp/$(APP_NAME) 96 | @cp $(APP_NAME) tmp/$(APP_NAME)/ 97 | @cp client/cli/go/heketi-cli tmp/$(APP_NAME)/ 98 | @cp etc/heketi.json tmp/$(APP_NAME)/ 99 | @mkdir -p $(DIR)/dist/ 100 | tar -czf $@ -C tmp $(APP_NAME); 101 | @rm -rf tmp 102 | @echo 103 | @echo Package $@ saved in dist directory 104 | 105 | $(CLIENT_PACKAGE): all 106 | @echo Packaging client Binaries... 107 | @mkdir -p tmp/$(CLIENT_PKG_NAME)/bin 108 | @mkdir -p tmp/$(CLIENT_PKG_NAME)/share/heketi/openshift/templates 109 | @mkdir -p tmp/$(CLIENT_PKG_NAME)/share/heketi/kubernetes 110 | @cp client/cli/go/topology-sample.json tmp/$(CLIENT_PKG_NAME)/share/heketi 111 | @cp client/cli/go/heketi-cli tmp/$(CLIENT_PKG_NAME)/bin 112 | @cp extras/openshift/templates/* tmp/$(CLIENT_PKG_NAME)/share/heketi/openshift/templates 113 | @cp extras/kubernetes/* tmp/$(CLIENT_PKG_NAME)/share/heketi/kubernetes 114 | @mkdir -p $(DIR)/dist/ 115 | tar -czf $@ -C tmp $(CLIENT_PKG_NAME); 116 | @rm -rf tmp 117 | @echo 118 | @echo Package $@ saved in dist directory 119 | 120 | dist: $(PACKAGE) $(CLIENT_PACKAGE) 121 | 122 | linux_amd64_dist: 123 | GOOS=linux GOARCH=amd64 $(MAKE) dist 124 | 125 | linux_arm_dist: 126 | GOOS=linux GOARCH=arm $(MAKE) dist 127 | 128 | linux_arm64_dist: 129 | GOOS=linux GOARCH=arm64 $(MAKE) dist 130 | 131 | darwin_amd64_dist: 132 | GOOS=darwin GOARCH=amd64 $(MAKE) dist 133 | 134 | release: darwin_amd64_dist linux_arm64_dist linux_arm_dist linux_amd64_dist 135 | 136 | .PHONY: server client test clean name run version release \ 137 | darwin_amd64_dist linux_arm_dist linux_amd64_dist linux_arm64_dist \ 138 | heketi 139 | -------------------------------------------------------------------------------- /executors/executor.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package executors 11 | 12 | import "encoding/xml" 13 | 14 | type Executor interface { 15 | GlusterdCheck(host string) error 16 | PeerProbe(exec_host, newnode string) error 17 | PeerDetach(exec_host, detachnode string) error 18 | DeviceSetup(host, device, vgid string) (*DeviceInfo, error) 19 | DeviceTeardown(host, device, vgid string) error 20 | BrickCreate(host string, brick *BrickRequest) (*BrickInfo, error) 21 | BrickDestroy(host string, brick *BrickRequest) error 22 | BrickDestroyCheck(host string, brick *BrickRequest) error 23 | VolumeCreate(host string, volume *VolumeRequest) (*Volume, error) 24 | VolumeDestroy(host string, volume string) error 25 | VolumeDestroyCheck(host, volume string) error 26 | VolumeExpand(host string, volume *VolumeRequest) (*Volume, error) 27 | VolumeReplaceBrick(host string, volume string, oldBrick *BrickInfo, newBrick *BrickInfo) error 28 | VolumeInfo(host string, volume string) (*Volume, error) 29 | SetLogLevel(level string) 30 | } 31 | 32 | // Enumerate durability types 33 | type DurabilityType int 34 | 35 | const ( 36 | DurabilityNone DurabilityType = iota 37 | DurabilityReplica 38 | DurabilityDispersion 39 | ) 40 | 41 | // Returns the size of the device 42 | type DeviceInfo struct { 43 | // Size in KB 44 | Size uint64 45 | ExtentSize uint64 46 | } 47 | 48 | // Brick description 49 | type BrickRequest struct { 50 | VgId string 51 | Name string 52 | TpSize uint64 53 | Size uint64 54 | PoolMetadataSize uint64 55 | Gid int64 56 | } 57 | 58 | // Returns information about the location of the brick 59 | type BrickInfo struct { 60 | Path string 61 | Host string 62 | } 63 | 64 | type VolumeRequest struct { 65 | Bricks []BrickInfo 66 | Name string 67 | Type DurabilityType 68 | GlusterVolumeOptions []string 69 | 70 | // Dispersion 71 | Data int 72 | Redundancy int 73 | 74 | // Replica 75 | Replica int 76 | } 77 | 78 | type Brick struct { 79 | UUID string `xml:"uuid,attr"` 80 | Name string `xml:"name"` 81 | HostUUID string `xml:"hostUuid"` 82 | IsArbiter int `xml:"isArbiter"` 83 | } 84 | 85 | type Bricks struct { 86 | XMLName xml.Name `xml:"bricks"` 87 | BrickList []Brick `xml:"brick"` 88 | } 89 | 90 | type Option struct { 91 | Name string `xml:"name"` 92 | Value string `xml:"value"` 93 | } 94 | 95 | type Options struct { 96 | XMLName xml.Name `xml:"options"` 97 | OptionList []Option `xml:"option"` 98 | } 99 | 100 | type Volume struct { 101 | XMLName xml.Name `xml:"volume"` 102 | VolumeName string `xml:"name"` 103 | ID string `xml:"id"` 104 | Status int `xml:"status"` 105 | StatusStr string `xml:"statusStr"` 106 | BrickCount int `xml:"brickCount"` 107 | DistCount int `xml:"distCount"` 108 | StripeCount int `xml:"stripeCount"` 109 | ReplicaCount int `xml:"replicaCount"` 110 | ArbiterCount int `xml:"arbiterCount"` 111 | DisperseCount int `xml:"disperseCount"` 112 | RedundancyCount int `xml:"redundancyCount"` 113 | Type int `xml:"type"` 114 | TypeStr string `xml:"typeStr"` 115 | Transport int `xml:"transport"` 116 | Bricks Bricks 117 | OptCount int `xml:"optCount"` 118 | Options Options 119 | } 120 | 121 | type Volumes struct { 122 | XMLName xml.Name `xml:"volumes"` 123 | Count int `xml:"count"` 124 | VolumeList []Volume `xml:"volume"` 125 | } 126 | 127 | type VolInfo struct { 128 | XMLName xml.Name `xml:"volInfo"` 129 | Volumes Volumes `xml:"volumes"` 130 | } 131 | -------------------------------------------------------------------------------- /pkg/utils/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package utils 11 | 12 | import ( 13 | "fmt" 14 | "io" 15 | "log" 16 | "os" 17 | "runtime" 18 | "strings" 19 | 20 | "github.com/lpabon/godbc" 21 | ) 22 | 23 | type LogLevel int 24 | 25 | // Log levels 26 | const ( 27 | LEVEL_NOLOG LogLevel = iota 28 | LEVEL_CRITICAL 29 | LEVEL_ERROR 30 | LEVEL_WARNING 31 | LEVEL_INFO 32 | LEVEL_DEBUG 33 | ) 34 | 35 | var ( 36 | stderr io.Writer = os.Stderr 37 | stdout io.Writer = os.Stdout 38 | ) 39 | 40 | type Logger struct { 41 | critlog, errorlog, infolog *log.Logger 42 | debuglog, warninglog *log.Logger 43 | 44 | level LogLevel 45 | } 46 | 47 | func logWithLongFile(l *log.Logger, format string, v ...interface{}) { 48 | _, file, line, _ := runtime.Caller(2) 49 | 50 | // Shorten the path. 51 | // From 52 | // /builddir/build/BUILD/heketi-3f4a5b1b6edff87232e8b24533c53b4151ebd9c7/src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go 53 | // to 54 | // src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go 55 | i := strings.Index(file, "/src/") 56 | if i == -1 { 57 | i = 0 58 | } 59 | 60 | l.Print(fmt.Sprintf("%v:%v: ", file[i:], line) + 61 | fmt.Sprintf(format, v...)) 62 | } 63 | 64 | // Create a new logger 65 | func NewLogger(prefix string, level LogLevel) *Logger { 66 | godbc.Require(level >= 0, level) 67 | godbc.Require(level <= LEVEL_DEBUG, level) 68 | 69 | l := &Logger{} 70 | 71 | if level == LEVEL_NOLOG { 72 | l.level = LEVEL_DEBUG 73 | } else { 74 | l.level = level 75 | } 76 | 77 | l.critlog = log.New(stderr, prefix+" CRITICAL ", log.LstdFlags) 78 | l.errorlog = log.New(stderr, prefix+" ERROR ", log.LstdFlags) 79 | l.warninglog = log.New(stdout, prefix+" WARNING ", log.LstdFlags) 80 | l.infolog = log.New(stdout, prefix+" INFO ", log.LstdFlags) 81 | l.debuglog = log.New(stdout, prefix+" DEBUG ", log.LstdFlags) 82 | 83 | godbc.Ensure(l.critlog != nil) 84 | godbc.Ensure(l.errorlog != nil) 85 | godbc.Ensure(l.warninglog != nil) 86 | godbc.Ensure(l.infolog != nil) 87 | godbc.Ensure(l.debuglog != nil) 88 | 89 | return l 90 | } 91 | 92 | // Return current level 93 | func (l *Logger) Level() LogLevel { 94 | return l.level 95 | } 96 | 97 | // Set level 98 | func (l *Logger) SetLevel(level LogLevel) { 99 | l.level = level 100 | } 101 | 102 | // Log critical information 103 | func (l *Logger) Critical(format string, v ...interface{}) { 104 | if l.level >= LEVEL_CRITICAL { 105 | logWithLongFile(l.critlog, format, v...) 106 | } 107 | } 108 | 109 | // Log error string 110 | func (l *Logger) LogError(format string, v ...interface{}) error { 111 | if l.level >= LEVEL_ERROR { 112 | logWithLongFile(l.errorlog, format, v...) 113 | } 114 | 115 | return fmt.Errorf(format, v...) 116 | } 117 | 118 | // Log error variable 119 | func (l *Logger) Err(err error) error { 120 | if l.level >= LEVEL_ERROR { 121 | logWithLongFile(l.errorlog, "%v", err) 122 | } 123 | 124 | return err 125 | } 126 | 127 | // Log warning information 128 | func (l *Logger) Warning(format string, v ...interface{}) { 129 | if l.level >= LEVEL_WARNING { 130 | l.warninglog.Printf(format, v...) 131 | } 132 | } 133 | 134 | // Log error variable as a warning 135 | func (l *Logger) WarnErr(err error) error { 136 | if l.level >= LEVEL_WARNING { 137 | logWithLongFile(l.warninglog, "%v", err) 138 | } 139 | 140 | return err 141 | } 142 | 143 | // Log string 144 | func (l *Logger) Info(format string, v ...interface{}) { 145 | if l.level >= LEVEL_INFO { 146 | l.infolog.Printf(format, v...) 147 | } 148 | } 149 | 150 | // Log string as debug 151 | func (l *Logger) Debug(format string, v ...interface{}) { 152 | if l.level >= LEVEL_DEBUG { 153 | logWithLongFile(l.debuglog, format, v...) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /apps/glusterfs/cluster_entry.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "bytes" 14 | "encoding/gob" 15 | "fmt" 16 | "sort" 17 | 18 | "github.com/boltdb/bolt" 19 | "github.com/heketi/heketi/pkg/glusterfs/api" 20 | "github.com/heketi/heketi/pkg/utils" 21 | "github.com/lpabon/godbc" 22 | ) 23 | 24 | type ClusterEntry struct { 25 | Info api.ClusterInfoResponse 26 | } 27 | 28 | func ClusterList(tx *bolt.Tx) ([]string, error) { 29 | 30 | list := EntryKeys(tx, BOLTDB_BUCKET_CLUSTER) 31 | if list == nil { 32 | return nil, ErrAccessList 33 | } 34 | return list, nil 35 | } 36 | 37 | func NewClusterEntry() *ClusterEntry { 38 | entry := &ClusterEntry{} 39 | entry.Info.Nodes = make(sort.StringSlice, 0) 40 | entry.Info.Volumes = make(sort.StringSlice, 0) 41 | 42 | return entry 43 | } 44 | 45 | func NewClusterEntryFromRequest() *ClusterEntry { 46 | entry := NewClusterEntry() 47 | entry.Info.Id = utils.GenUUID() 48 | 49 | return entry 50 | } 51 | 52 | func NewClusterEntryFromId(tx *bolt.Tx, id string) (*ClusterEntry, error) { 53 | 54 | entry := NewClusterEntry() 55 | err := EntryLoad(tx, entry, id) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return entry, nil 61 | } 62 | 63 | func (c *ClusterEntry) BucketName() string { 64 | return BOLTDB_BUCKET_CLUSTER 65 | } 66 | 67 | func (c *ClusterEntry) Save(tx *bolt.Tx) error { 68 | godbc.Require(tx != nil) 69 | godbc.Require(len(c.Info.Id) > 0) 70 | 71 | return EntrySave(tx, c, c.Info.Id) 72 | } 73 | 74 | func (c *ClusterEntry) ConflictString() string { 75 | return fmt.Sprintf("Unable to delete cluster [%v] because it contains volumes and/or nodes", c.Info.Id) 76 | } 77 | 78 | func (c *ClusterEntry) Delete(tx *bolt.Tx) error { 79 | godbc.Require(tx != nil) 80 | 81 | // Check if the cluster still has nodes or volumes 82 | if len(c.Info.Nodes) > 0 || len(c.Info.Volumes) > 0 { 83 | logger.Warning(c.ConflictString()) 84 | return ErrConflict 85 | } 86 | 87 | return EntryDelete(tx, c, c.Info.Id) 88 | } 89 | 90 | func (c *ClusterEntry) NewClusterInfoResponse(tx *bolt.Tx) (*api.ClusterInfoResponse, error) { 91 | 92 | info := &api.ClusterInfoResponse{} 93 | *info = c.Info 94 | 95 | return info, nil 96 | } 97 | 98 | func (c *ClusterEntry) Marshal() ([]byte, error) { 99 | var buffer bytes.Buffer 100 | enc := gob.NewEncoder(&buffer) 101 | err := enc.Encode(*c) 102 | 103 | return buffer.Bytes(), err 104 | } 105 | 106 | func (c *ClusterEntry) Unmarshal(buffer []byte) error { 107 | dec := gob.NewDecoder(bytes.NewReader(buffer)) 108 | err := dec.Decode(c) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | // Make sure to setup arrays if nil 114 | if c.Info.Nodes == nil { 115 | c.Info.Nodes = make(sort.StringSlice, 0) 116 | } 117 | if c.Info.Volumes == nil { 118 | c.Info.Volumes = make(sort.StringSlice, 0) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func (c *ClusterEntry) NodeEntryFromClusterIndex(tx *bolt.Tx, index int) (*NodeEntry, error) { 125 | node, err := NewNodeEntryFromId(tx, c.Info.Nodes[index]) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | return node, nil 131 | } 132 | 133 | func (c *ClusterEntry) NodeAdd(id string) { 134 | c.Info.Nodes = append(c.Info.Nodes, id) 135 | c.Info.Nodes.Sort() 136 | } 137 | 138 | func (c *ClusterEntry) VolumeAdd(id string) { 139 | c.Info.Volumes = append(c.Info.Volumes, id) 140 | c.Info.Volumes.Sort() 141 | } 142 | 143 | func (c *ClusterEntry) VolumeDelete(id string) { 144 | c.Info.Volumes = utils.SortedStringsDelete(c.Info.Volumes, id) 145 | } 146 | 147 | func (c *ClusterEntry) NodeDelete(id string) { 148 | c.Info.Nodes = utils.SortedStringsDelete(c.Info.Nodes, id) 149 | } 150 | 151 | func ClusterEntryUpgrade(tx *bolt.Tx) error { 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /extras/kubernetes/heketi-deployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "List", 3 | "apiVersion": "v1", 4 | "items": [ 5 | { 6 | "kind": "Secret", 7 | "apiVersion": "v1", 8 | "metadata": { 9 | "name": "heketi-db-backup", 10 | "labels": { 11 | "glusterfs": "heketi-db", 12 | "heketi": "db" 13 | } 14 | }, 15 | "data": { 16 | "heketi.db": "" 17 | }, 18 | "type": "Opaque" 19 | }, 20 | { 21 | "kind": "Service", 22 | "apiVersion": "v1", 23 | "metadata": { 24 | "name": "heketi", 25 | "labels": { 26 | "glusterfs": "heketi-service", 27 | "deploy-heketi": "support" 28 | }, 29 | "annotations": { 30 | "description": "Exposes Heketi Service" 31 | } 32 | }, 33 | "spec": { 34 | "selector": { 35 | "name": "heketi" 36 | }, 37 | "ports": [ 38 | { 39 | "name": "heketi", 40 | "port": 8080, 41 | "targetPort": 8080 42 | } 43 | ] 44 | } 45 | }, 46 | { 47 | "kind": "Deployment", 48 | "apiVersion": "extensions/v1beta1", 49 | "metadata": { 50 | "name": "heketi", 51 | "labels": { 52 | "glusterfs": "heketi-deployment" 53 | }, 54 | "annotations": { 55 | "description": "Defines how to deploy Heketi" 56 | } 57 | }, 58 | "spec": { 59 | "replicas": 1, 60 | "template": { 61 | "metadata": { 62 | "name": "heketi", 63 | "labels": { 64 | "name": "heketi", 65 | "glusterfs": "heketi-pod" 66 | } 67 | }, 68 | "spec": { 69 | "serviceAccountName": "heketi-service-account", 70 | "containers": [ 71 | { 72 | "image": "heketi/heketi:dev", 73 | "imagePullPolicy": "Always", 74 | "name": "heketi", 75 | "env": [ 76 | { 77 | "name": "HEKETI_EXECUTOR", 78 | "value": "kubernetes" 79 | }, 80 | { 81 | "name": "HEKETI_FSTAB", 82 | "value": "/var/lib/heketi/fstab" 83 | }, 84 | { 85 | "name": "HEKETI_SNAPSHOT_LIMIT", 86 | "value": "14" 87 | }, 88 | { 89 | "name": "HEKETI_KUBE_GLUSTER_DAEMONSET", 90 | "value": "y" 91 | } 92 | ], 93 | "ports": [ 94 | { 95 | "containerPort": 8080 96 | } 97 | ], 98 | "volumeMounts": [ 99 | { 100 | "mountPath": "/backupdb", 101 | "name": "heketi-db-secret" 102 | }, 103 | { 104 | "name": "db", 105 | "mountPath": "/var/lib/heketi" 106 | } 107 | ], 108 | "readinessProbe": { 109 | "timeoutSeconds": 3, 110 | "initialDelaySeconds": 3, 111 | "httpGet": { 112 | "path": "/hello", 113 | "port": 8080 114 | } 115 | }, 116 | "livenessProbe": { 117 | "timeoutSeconds": 3, 118 | "initialDelaySeconds": 30, 119 | "httpGet": { 120 | "path": "/hello", 121 | "port": 8080 122 | } 123 | } 124 | } 125 | ], 126 | "volumes": [ 127 | { 128 | "name": "db" 129 | }, 130 | { 131 | "name": "heketi-db-secret", 132 | "secret": { 133 | "secretName": "heketi-db-backup" 134 | } 135 | } 136 | ] 137 | } 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /client/api/go-client/device.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), as published by the Free Software Foundation, 7 | // or under the Apache License, Version 2.0 . 9 | // 10 | // You may not use this file except in compliance with those terms. 11 | // 12 | 13 | package client 14 | 15 | import ( 16 | "bytes" 17 | "encoding/json" 18 | "net/http" 19 | "time" 20 | 21 | "github.com/heketi/heketi/pkg/glusterfs/api" 22 | "github.com/heketi/heketi/pkg/utils" 23 | ) 24 | 25 | func (c *Client) DeviceAdd(request *api.DeviceAddRequest) error { 26 | // Marshal request to JSON 27 | buffer, err := json.Marshal(request) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Create a request 33 | req, err := http.NewRequest("POST", c.host+"/devices", bytes.NewBuffer(buffer)) 34 | if err != nil { 35 | return err 36 | } 37 | req.Header.Set("Content-Type", "application/json") 38 | 39 | // Set token 40 | err = c.setToken(req) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // Send request 46 | r, err := c.do(req) 47 | if err != nil { 48 | return err 49 | } 50 | if r.StatusCode != http.StatusAccepted { 51 | return utils.GetErrorFromResponse(r) 52 | } 53 | 54 | // Wait for response 55 | r, err = c.waitForResponseWithTimer(r, time.Second) 56 | if err != nil { 57 | return err 58 | } 59 | if r.StatusCode != http.StatusNoContent { 60 | return utils.GetErrorFromResponse(r) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) { 67 | 68 | // Create request 69 | req, err := http.NewRequest("GET", c.host+"/devices/"+id, nil) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | // Set token 75 | err = c.setToken(req) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | // Get info 81 | r, err := c.do(req) 82 | if err != nil { 83 | return nil, err 84 | } 85 | if r.StatusCode != http.StatusOK { 86 | return nil, utils.GetErrorFromResponse(r) 87 | } 88 | 89 | // Read JSON response 90 | var device api.DeviceInfoResponse 91 | err = utils.GetJsonFromResponse(r, &device) 92 | r.Body.Close() 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | return &device, nil 98 | } 99 | 100 | func (c *Client) DeviceDelete(id string) error { 101 | 102 | // Create a request 103 | req, err := http.NewRequest("DELETE", c.host+"/devices/"+id, nil) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | // Set token 109 | err = c.setToken(req) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | // Send request 115 | r, err := c.do(req) 116 | if err != nil { 117 | return err 118 | } 119 | if r.StatusCode != http.StatusAccepted { 120 | return utils.GetErrorFromResponse(r) 121 | } 122 | 123 | // Wait for response 124 | r, err = c.waitForResponseWithTimer(r, time.Second) 125 | if err != nil { 126 | return err 127 | } 128 | if r.StatusCode != http.StatusNoContent { 129 | return utils.GetErrorFromResponse(r) 130 | } 131 | 132 | return nil 133 | } 134 | 135 | func (c *Client) DeviceState(id string, 136 | request *api.StateRequest) error { 137 | 138 | // Marshal request to JSON 139 | buffer, err := json.Marshal(request) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | // Create a request 145 | req, err := http.NewRequest("POST", 146 | c.host+"/devices/"+id+"/state", 147 | bytes.NewBuffer(buffer)) 148 | if err != nil { 149 | return err 150 | } 151 | req.Header.Set("Content-Type", "application/json") 152 | 153 | // Set token 154 | err = c.setToken(req) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | // Get info 160 | r, err := c.do(req) 161 | if err != nil { 162 | return err 163 | } 164 | if r.StatusCode != http.StatusAccepted { 165 | return utils.GetErrorFromResponse(r) 166 | } 167 | 168 | // Wait for response 169 | r, err = c.waitForResponseWithTimer(r, time.Second) 170 | if err != nil { 171 | return err 172 | } 173 | if r.StatusCode != http.StatusNoContent { 174 | return utils.GetErrorFromResponse(r) 175 | } 176 | 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /apps/glusterfs/app_cluster.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), or the GNU General Public License, version 2 (GPLv2), in all 7 | // cases as published by the Free Software Foundation. 8 | // 9 | 10 | package glusterfs 11 | 12 | import ( 13 | "encoding/json" 14 | "net/http" 15 | 16 | "github.com/boltdb/bolt" 17 | "github.com/gorilla/mux" 18 | "github.com/heketi/heketi/pkg/glusterfs/api" 19 | ) 20 | 21 | func (a *App) ClusterCreate(w http.ResponseWriter, r *http.Request) { 22 | 23 | // Create a new ClusterInfo 24 | entry := NewClusterEntryFromRequest() 25 | 26 | // Add cluster to db 27 | err := a.db.Update(func(tx *bolt.Tx) error { 28 | err := entry.Save(tx) 29 | if err != nil { 30 | http.Error(w, err.Error(), http.StatusInternalServerError) 31 | return err 32 | } 33 | 34 | return nil 35 | 36 | }) 37 | if err != nil { 38 | return 39 | } 40 | 41 | // Send back we created it (as long as we did not fail) 42 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 43 | w.WriteHeader(http.StatusCreated) 44 | if err := json.NewEncoder(w).Encode(entry.Info); err != nil { 45 | panic(err) 46 | } 47 | } 48 | 49 | func (a *App) ClusterList(w http.ResponseWriter, r *http.Request) { 50 | 51 | var list api.ClusterListResponse 52 | 53 | // Get all the cluster ids from the DB 54 | err := a.db.View(func(tx *bolt.Tx) error { 55 | var err error 56 | 57 | list.Clusters, err = ClusterList(tx) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return nil 63 | }) 64 | 65 | if err != nil { 66 | logger.Err(err) 67 | http.Error(w, err.Error(), http.StatusInternalServerError) 68 | return 69 | } 70 | 71 | // Send list back 72 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 73 | w.WriteHeader(http.StatusOK) 74 | if err := json.NewEncoder(w).Encode(list); err != nil { 75 | panic(err) 76 | } 77 | } 78 | 79 | func (a *App) ClusterInfo(w http.ResponseWriter, r *http.Request) { 80 | 81 | // Get the id from the URL 82 | vars := mux.Vars(r) 83 | id := vars["id"] 84 | 85 | // Get info from db 86 | var info *api.ClusterInfoResponse 87 | err := a.db.View(func(tx *bolt.Tx) error { 88 | 89 | // Create a db entry from the id 90 | entry, err := NewClusterEntryFromId(tx, id) 91 | if err == ErrNotFound { 92 | http.Error(w, err.Error(), http.StatusNotFound) 93 | return err 94 | } else if err != nil { 95 | http.Error(w, err.Error(), http.StatusInternalServerError) 96 | return err 97 | } 98 | 99 | // Create a response from the db entry 100 | info, err = entry.NewClusterInfoResponse(tx) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | return nil 106 | }) 107 | if err != nil { 108 | return 109 | } 110 | 111 | // Write msg 112 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 113 | w.WriteHeader(http.StatusOK) 114 | if err := json.NewEncoder(w).Encode(info); err != nil { 115 | panic(err) 116 | } 117 | 118 | } 119 | 120 | func (a *App) ClusterDelete(w http.ResponseWriter, r *http.Request) { 121 | 122 | // Get the id from the URL 123 | vars := mux.Vars(r) 124 | id := vars["id"] 125 | 126 | // Delete cluster from db 127 | err := a.db.Update(func(tx *bolt.Tx) error { 128 | 129 | // Access cluster entry 130 | entry, err := NewClusterEntryFromId(tx, id) 131 | if err == ErrNotFound { 132 | http.Error(w, err.Error(), http.StatusNotFound) 133 | return err 134 | } else if err != nil { 135 | http.Error(w, err.Error(), http.StatusInternalServerError) 136 | return logger.Err(err) 137 | } 138 | 139 | err = entry.Delete(tx) 140 | if err != nil { 141 | if err == ErrConflict { 142 | http.Error(w, entry.ConflictString(), http.StatusConflict) 143 | } else { 144 | http.Error(w, err.Error(), http.StatusInternalServerError) 145 | } 146 | return err 147 | } 148 | 149 | return nil 150 | }) 151 | if err != nil { 152 | return 153 | } 154 | 155 | // Update allocator hat the cluster has been removed 156 | a.allocator.RemoveCluster(id) 157 | 158 | // Show that the key has been deleted 159 | logger.Info("Deleted cluster [%s]", id) 160 | 161 | // Write msg 162 | w.WriteHeader(http.StatusOK) 163 | } 164 | -------------------------------------------------------------------------------- /client/api/go-client/node.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 The heketi Authors 3 | // 4 | // This file is licensed to you under your choice of the GNU Lesser 5 | // General Public License, version 3 or any later version (LGPLv3 or 6 | // later), as published by the Free Software Foundation, 7 | // or under the Apache License, Version 2.0 . 9 | // 10 | // You may not use this file except in compliance with those terms. 11 | // 12 | 13 | package client 14 | 15 | import ( 16 | "bytes" 17 | "encoding/json" 18 | "net/http" 19 | "time" 20 | 21 | "github.com/heketi/heketi/pkg/glusterfs/api" 22 | "github.com/heketi/heketi/pkg/utils" 23 | ) 24 | 25 | func (c *Client) NodeAdd(request *api.NodeAddRequest) (*api.NodeInfoResponse, error) { 26 | 27 | // Marshal request to JSON 28 | buffer, err := json.Marshal(request) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | // Create a request 34 | req, err := http.NewRequest("POST", c.host+"/nodes", bytes.NewBuffer(buffer)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | req.Header.Set("Content-Type", "application/json") 39 | 40 | // Set token 41 | err = c.setToken(req) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | // Send request 47 | r, err := c.do(req) 48 | if err != nil { 49 | return nil, err 50 | } 51 | if r.StatusCode != http.StatusAccepted { 52 | return nil, utils.GetErrorFromResponse(r) 53 | } 54 | 55 | // Wait for response 56 | r, err = c.waitForResponseWithTimer(r, time.Millisecond*250) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if r.StatusCode != http.StatusOK { 61 | return nil, utils.GetErrorFromResponse(r) 62 | } 63 | 64 | // Read JSON response 65 | var node api.NodeInfoResponse 66 | err = utils.GetJsonFromResponse(r, &node) 67 | r.Body.Close() 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return &node, nil 73 | } 74 | 75 | func (c *Client) NodeInfo(id string) (*api.NodeInfoResponse, error) { 76 | 77 | // Create request 78 | req, err := http.NewRequest("GET", c.host+"/nodes/"+id, nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | // Set token 84 | err = c.setToken(req) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | // Get info 90 | r, err := c.do(req) 91 | if err != nil { 92 | return nil, err 93 | } 94 | if r.StatusCode != http.StatusOK { 95 | return nil, utils.GetErrorFromResponse(r) 96 | } 97 | 98 | // Read JSON response 99 | var node api.NodeInfoResponse 100 | err = utils.GetJsonFromResponse(r, &node) 101 | r.Body.Close() 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return &node, nil 107 | } 108 | 109 | func (c *Client) NodeDelete(id string) error { 110 | 111 | // Create a request 112 | req, err := http.NewRequest("DELETE", c.host+"/nodes/"+id, nil) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | // Set token 118 | err = c.setToken(req) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | // Send request 124 | r, err := c.do(req) 125 | if err != nil { 126 | return err 127 | } 128 | if r.StatusCode != http.StatusAccepted { 129 | return utils.GetErrorFromResponse(r) 130 | } 131 | 132 | // Wait for response 133 | r, err = c.waitForResponseWithTimer(r, time.Millisecond*250) 134 | if err != nil { 135 | return err 136 | } 137 | if r.StatusCode != http.StatusNoContent { 138 | return utils.GetErrorFromResponse(r) 139 | } 140 | 141 | return nil 142 | } 143 | 144 | func (c *Client) NodeState(id string, request *api.StateRequest) error { 145 | // Marshal request to JSON 146 | buffer, err := json.Marshal(request) 147 | if err != nil { 148 | return err 149 | } 150 | 151 | // Create a request 152 | req, err := http.NewRequest("POST", 153 | c.host+"/nodes/"+id+"/state", 154 | bytes.NewBuffer(buffer)) 155 | if err != nil { 156 | return err 157 | } 158 | req.Header.Set("Content-Type", "application/json") 159 | 160 | // Set token 161 | err = c.setToken(req) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | // Get info 167 | r, err := c.do(req) 168 | if err != nil { 169 | return err 170 | } 171 | if r.StatusCode != http.StatusAccepted { 172 | return utils.GetErrorFromResponse(r) 173 | } 174 | 175 | // Wait for response 176 | r, err = c.waitForResponseWithTimer(r, time.Second) 177 | if err != nil { 178 | return err 179 | } 180 | if r.StatusCode != http.StatusNoContent { 181 | return utils.GetErrorFromResponse(r) 182 | } 183 | 184 | return nil 185 | } 186 | --------------------------------------------------------------------------------