Commit 56755968 authored by Remon Huijts's avatar Remon Huijts
Browse files

Merge branch '6-improve-error-handling' into 'master'

Resolve "Improve error handling"

Closes #6

See merge request !3
parents d3e56f66 71ee5be1
Pipeline #8430 passed with stages
in 1 minute and 3 seconds
......@@ -21,6 +21,9 @@ build:
paths:
- docs/_build/html
expire_in: 1 week
only:
changes:
- docs/**/*
deploy-master:
stage: deploy
......@@ -35,3 +38,6 @@ deploy-master:
- apk update
- apk add openssh-client rsync bash
- cd docs && ./deploy.sh
only:
changes:
- docs/**/*
......@@ -12,7 +12,9 @@ Note: this needs to be done by Greenhost employees at the moment.
relevant user, then go to "User settings", tab "API keys"); a new one will be
generated automatically when the VPS is stopped and started from Cosmos2.
5. Reboot the VPSs after changing the `api_enabled` property, and also if you
did something at step 4.
did something at step 4. Be careful: the new api key that is created for the
machine is coupled to the user as which you are logged in when you restart
the VPS!
## Install driver and provisioner
......
#!/bin/bash
set -o errexit
DRIVER_LOG=${DRIVER_LOG:-"/tmp/flex-driver.log"}
# Write output to stdout.
# Write program result to stdout.
output() {
printf "$*" >&1
status=$1
message=$2
# Format the JSON output.
jq -n -c \
--arg status "$status" \
--arg message "$message" \
'{$status, $message}' \
>&1
}
exitWithFailure() {
debug "Fatal error: ${1-}"
output "Failure" "${1-}"
exit 1
}
# Write timestamped text to the log file.
......@@ -54,23 +69,28 @@ getParams() {
debug "disk id: $diskID"
}
# Call the Cosmos2 API to perform an action on a disk image.
ghostAction() {
debug "ghostAction $*"
# Action to perform: "attach" or "detach".
local action=$1
local apiToken=$2
# Numerical ID of disk image.
local diskImage=$3
# Numerical ID of VPS.
local vps=$4
# URL to the Cosmos2 instance to talk to.
local server="$cosmosUrl"
case $action in
attach) ;&
detach) ;;
*)
output "{\"status\": \"Unsupported action\"}"
output "Unsupported action"
exit 0
esac
local url="${server}/api/v2/disks/$diskImage/actions"
debug "curling cosmos: $url"
if [ -z "$vps" ]
if [[ -z "$vps" ]]
then
local data="{\"type\": \"$action\"}"
else
......@@ -80,6 +100,13 @@ ghostAction() {
# Record the complete body from the HTTP response in the log.
debug "output: "
debug $(jq '.' <<<"$response")
errorOccurred=$(jq '."error"' <<<"$response")
if [[ "$errorOccurred" = "true" ]]
then
errorMessage=$(jq '."message"' <<<"$response")
errorMessage="The Cosmos2 api call returned an error: $errorMessage"
return 1
fi
# Store the `disk_slot` field of the output in a variable for later use.
diskSlot=$(jq '."disk_slot"' <<<"$response")
}
......@@ -96,20 +123,38 @@ domount() {
# which disk to detach; also
# 2. even if we could detach directly after unmount, there might be an error
# during detach, which we need to be able to recover from.
ghostAction "detach" "$apiToken" "$diskID"
if ! ghostAction "detach" "$apiToken" "$diskID"
then
exitWithFailure "$errorMessage"
fi
# Now tell Cosmos2 to attach the disk to the right VPS.
ghostAction "attach" "$apiToken" "$diskID" "$vpsID"
if ! ghostAction "attach" "$apiToken" "$diskID" "$vpsID"
then
exitWithFailure "$errorMessage"
fi
debug "disk_slot: $diskSlot"
# Do a sanity check on the disk slot number.
if ! [[ "$diskSlot" =~ ^[0-9]+$ ]]
then
errorMessage="disk_slot is not a non-negative integer: $diskSlot"
exitWithFailure "$errorMessage"
fi
if [[ $diskSlot -eq 0 ]]
then
errorMessage="disk_slot is 0; we refuse to mount /dev/xvda1 as that is usually the OS disk!"
exitWithFailure "$errorMessage"
fi
# Convert numeric disk slot index to device letter, so
# 0 becomes a, 1 becomes b, etc.
diskLetter=$(printf \\$(printf '%03o' $((97 + $diskSlot))))
device="/dev/xvd${diskLetter}1"
debug "device: $device"
# Use the `blkid` program to scan the device for an existing filesystem.
existingFileSystem=$(blkid "$device")
existingFileSystem=$(lsblk -n -o FSTYPE "$device")
debug "blkid: $existingFileSystem"
if [ -z "$existingFileSystem" ]
if [[ -z "$existingFileSystem" ]]
then
debug "No existing filesystem; creating one."
mkfs.xfs "$device" >/dev/null 2>&1
......@@ -122,7 +167,7 @@ domount() {
# Mount the virtual block device at the given mount point.
mount "$device" "$mountPath" >/dev/null 2>&1
output "{\"status\":\"Success\"}"
output "Success"
exit 0
}
......@@ -136,16 +181,17 @@ unmount() {
# enough information to do so. Instead, we do a just-in-time detach when
# the disk is attached later on.
output "{\"status\":\"Success\"}"
output "Success"
exit 0
}
# This is the command Kubernetes wants us to perform.
op=$1
if [ "$op" = "init" ]; then
if [[ "$op" = "init" ]]
then
debug "init $@"
output "{\"status\":\"Success\",\"capabilities\":{\"attach\":false}}"
echo "{\"status\":\"Success\",\"capabilities\":{\"attach\":false}}" >&1
exit 0
fi
......@@ -160,6 +206,6 @@ case "$op" in
;;
*)
debug "not supported: $op $*"
output "{\"status\":\"Not supported\"}"
echo "{\"status\":\"Not supported\"}" >&1
exit 0
esac
......@@ -71,6 +71,7 @@ func (p *GreenhostProvisioner) Provision(options controller.VolumeOptions) (*v1.
ProjectID: p.projectID,
}
// The Cosmos API returns a description of the created disk image.
glog.Infof("Creating new disk image with specs %v", specs)
diskImage, err := p.cosmosClient.CreateDiskImage(&specs)
if err != nil {
glog.Warningf("Error in creating new disk image %v", err)
......
......@@ -3,6 +3,7 @@ package ghost
import (
"encoding/json"
"errors"
"io/ioutil"
"github.com/golang/glog"
)
......@@ -33,6 +34,14 @@ func (cc *CosmosClient) CreateDiskImage(specs *Specs) (*DiskImage, error) {
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = errors.New("Cosmos2 could not create the new disk image: " + string(responseData))
return nil, err
}
var diskResponse diskResponse
json.NewDecoder(resp.Body).Decode(&diskResponse)
diskImage := diskResponse.DiskImage
......@@ -55,14 +64,19 @@ func (cc *CosmosClient) detachDiskImage(id string) bool {
resp, err := cc.ApiCall("POST", "/disks/"+id+"/actions", postData)
if err != nil {
glog.Warningf("Error when detaching disk: %v", err)
glog.Warningf("Error performing API call to Cosmos2: %v", err)
return false
}
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
return true
} else {
glog.Warningf("Error when detaching disk: %d", resp.StatusCode)
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
glog.Warningf("Error reading Cosmos2 response: %v", err)
return false
}
glog.Warningf("Cosmos2 failed to detach disk: %d %s", resp.StatusCode, responseData)
return false
}
}
......@@ -75,14 +89,19 @@ func (cc *CosmosClient) DeleteDiskImage(id string) bool {
resp, err := cc.ApiCall("DELETE", "/disks/"+id, nil)
if err != nil {
glog.Warningf("Error when deleting disk: %v", err)
glog.Warningf("Error performing API call to Cosmos2: %v", err)
return false
}
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
return true
} else {
glog.Warningf("Error when deleting disk: %d", resp.StatusCode)
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
glog.Warningf("Error reading Cosmos2 response: %v", err)
return false
}
glog.Warningf("Cosmos2 failed to delete disk with ID %s returning: %d %s", id, resp.StatusCode, responseData)
return false
}
}
......@@ -103,13 +122,21 @@ type Droplet struct {
func (cc *CosmosClient) GetInstanceData() (*string, error) {
resp, err := cc.ApiCall("GET", "/droplets/"+cc.InstanceID, nil)
if err != nil {
glog.Fatalf("Could not get instance data: %v", err)
return nil, err
}
if resp.StatusCode >= 400 {
responseData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = errors.New("Cosmos2 could not get the instance data: " + string(responseData))
return nil, err
}
var instanceResponse InstanceResponse
err = json.NewDecoder(resp.Body).Decode(&instanceResponse)
if err != nil {
glog.Fatalf("Error in JSON decoding: %v", err)
return nil, err
}
// `nil` means the `project_id` field is not present in the response json.
projectID := instanceResponse.Droplet.ProjectID
......
provisioner:
image:
repository: "docker.greenhost.net/open/kubernetes-ghost/provisioner"
tag: "0.1.0"
imagePullPolicy: "Always"
tag: "0.2.0"
pullPolicy: "Always"
cosmos_api_url: "https://service.greenhost.net/api/v2"
cosmos_location: "ams1"
driver:
image:
repository: "docker.greenhost.net/open/kubernetes-ghost/driver"
tag: "0.1.0"
imagePullPolicy: "Always"
tag: "0.2.0"
pullPolicy: "Always"
# Make this storage class the default, so persistent volume claims that do not
# specify a class are served by this provisioner.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment