1
"""Common python utilities for the nova provider"""
3
from charmhelpers.core import hookenv
8
from hooks import REQUIRED_CONFIGURATION_OPTIONS
10
METADATA_URL = "http://169.254.169.254/openstack/2012-08-10/meta_data.json"
11
NOVA_ENVIRONMENT_MAP = {
12
"endpoint": "OS_AUTH_URL", "region": "OS_REGION_NAME",
13
"tenant": "OS_TENANT_NAME", "key": "OS_USERNAME", "secret": "OS_PASSWORD"}
16
def load_environment():
18
Source our credentials from the configuration definitions into our
21
config_data = hookenv.config()
22
for option in REQUIRED_CONFIGURATION_OPTIONS:
23
environment_variable = NOVA_ENVIRONMENT_MAP[option]
24
os.environ[environment_variable] = config_data[option].strip()
25
validate_credentials()
28
def validate_credentials():
29
"""Attempt to contact nova volume service or exit(1)"""
31
subprocess.check_call("nova list", shell=True)
32
except subprocess.CalledProcessError, e:
34
"ERROR: Charm configured credentials can't access endpoint. %s" %
39
"Validated charm configuration credentials have access to block "
43
def get_volume_attachments(volume_id):
44
"""Return a C{list} of volume attachments if present"""
45
from ast import literal_eval
47
output = subprocess.check_output(
48
"nova volume-show %s | grep attachments | awk -F '|' '{print $3}'"
49
% volume_id, shell=True)
50
attachments = literal_eval(output.strip())
51
except subprocess.CalledProcessError:
56
def volume_exists(volume_id):
57
"""Returns C{True} when C{volume_id} already exists"""
59
subprocess.check_call(
60
"nova volume-show %s" % volume_id, shell=True)
61
except subprocess.CalledProcessError:
66
def get_volume_id(volume_designation=None, instance_id=None):
67
"""Return the nova volume id associated with this unit
69
Optionally, C{volume_designation} can be either a volume-id or
70
volume-display-name and the matching C{volume-id} will be returned.
71
If no matching volume is found, return C{None}.
73
token = volume_designation
75
# Get volume by name or volume-id
76
# nova volume-show will error if multiple matches are found
78
"nova volume-show '%s' | grep ' id ' | awk '{ print $4 }'" % token)
80
command = "nova volume-list | grep %s | awk '{print $2}'" % instance_id
82
# Find volume by unit name
83
token = hookenv.remote_unit()
84
command = "nova volume-list | grep %s | awk '{print $2}'" % token
87
output = subprocess.check_output(command, shell=True)
88
except subprocess.CalledProcessError, e:
90
"ERROR: Couldn't find nova volume id for %s. %s" % (token, str(e)),
94
lines = output.strip().split("\n")
97
"Error: Multiple nova volumes labeled as associated with "
98
"%s. Cannot get_volume_id." % token, hookenv.ERROR)
105
def get_volume_status(volume_designation=None):
106
"""Return the status of a nova volume
107
If C{volume_designation} is specified, return the status of that volume,
108
otherwise use L{get_volume_id} to grab the volume currently related to
109
this unit. If no volume is discoverable, return C{None}.
111
if volume_designation is None:
112
volume_designation = get_volume_id()
113
if volume_designation is None:
115
"WARNING: Can't find volume_id to get status.",
119
output = subprocess.check_output(
120
"nova volume-show '%s' | grep ' status ' | awk '{ print $4 }'"
121
% volume_designation, shell=True)
122
except subprocess.CalledProcessError, e:
124
"Error: nova couldn't get status of volume %s. %s" %
125
(volume_designation, str(e)), hookenv.ERROR)
127
return output.strip()
130
def attach_nova_volume(instance_id, volume_id=None, size=None,
133
Run nova commands to create and attach a volume to the remote unit if none
134
exists. Attempt to attach and validate the attached volume 10 times. If
135
unable to resolve the attach issues, exit in error and log the issue.
137
Log errors if the nova volume is in an unsupported state, and if C{in-use}
138
report it is already attached.
139
Return the device-path of the attached volume to the caller.
141
load_environment() # Will fail if proper environment is not set up
142
remote_unit = hookenv.remote_unit()
143
if volume_label is None:
144
volume_label = "%s unit volume" % remote_unit
146
if not volume_exists(volume_id):
148
"Requested volume-id (%s) does not exist. Unable to associate "
149
"storage with %s" % (volume_id, remote_unit),
153
# Validate that current volume status is supported
154
status = get_volume_status(volume_id)
155
if status in ["in-use", "attaching"]:
156
hookenv.log("Volume %s already attached. Done" % volume_id)
157
attachment = get_volume_attachments(volume_id)[0]
158
return attachment["device"] # The device path on the instance
159
if status != "available":
161
"Cannot attach nova volume. Volume has unsupported status: %s"
165
# No volume_id, create a new volume if one isn't already created for
166
# this JUJU_REMOTE_UNIT
167
volume_id = get_volume_id(volume_label)
170
size = hookenv.config("default_volume_size")
172
"Creating a %sGig volume named (%s) for instance %s" %
173
(size, volume_label, instance_id))
174
subprocess.check_call(
175
"nova volume-create --display-name '%s' %s" %
176
(volume_label, size), shell=True)
177
# Get new volume_id search for remote_unit in volume label
178
volume_id = get_volume_id(volume_label)
181
hookenv.log("Attaching %s (%s)" % (volume_label, volume_id))
183
status = get_volume_status(volume_id)
184
if status == "in-use":
185
attachment = get_volume_attachments(volume_id)[0]
186
return attachment["device"] # The device path on the instance
187
if status == "available":
188
device = subprocess.check_output(
189
"nova volume-attach %s %s auto | egrep -o \"/dev/vd[b-z]\"" %
190
(instance_id, volume_id), shell=True)
197
"ERROR: Unable to discover device attached by nova volume-attach",
200
return device.strip()
203
def detach_nova_volume(instance_id):
204
"""Use nova commands to detach a volume from remote unit if present"""
205
load_environment() # Will fail if proper environment is not set up
206
volume_id = get_volume_id(instance_id=instance_id)
208
status = get_volume_status(volume_id)
210
hookenv.log("Cannot find volume name to detach, done")
213
if status == "available":
214
hookenv.log("Volume (%s) already detached. Done" % volume_id)
218
"Detaching volume (%s) from instance %s" % (volume_id, instance_id))
220
subprocess.check_call(
221
"nova volume-detach %s %s" % (instance_id, volume_id), shell=True)
222
except subprocess.CalledProcessError, e:
224
"ERROR: Couldn't detach nova volume %s. %s" % (volume_id, str(e)),