~nottrobin/charms/precise/block-storage-broker/ensure-python-apt

« back to all changes in this revision

Viewing changes to hooks/nova_util.py

Merge bsb-ec2-support [f=1298496] [r=dpb,fcorrea]

Add EC2 support to block-storage-broker to create, attach, label and detach ec2 volumes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""Common python utilities for the nova provider"""
2
 
 
3
 
from charmhelpers.core import hookenv
4
 
import subprocess
5
 
import os
6
 
import sys
7
 
from time import sleep
8
 
from hooks import REQUIRED_CONFIGURATION_OPTIONS
9
 
 
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"}
14
 
 
15
 
 
16
 
def load_environment():
17
 
    """
18
 
    Source our credentials from the configuration definitions into our
19
 
    operating environment
20
 
    """
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()
26
 
 
27
 
 
28
 
def validate_credentials():
29
 
    """Attempt to contact nova volume service or exit(1)"""
30
 
    try:
31
 
        subprocess.check_call("nova list", shell=True)
32
 
    except subprocess.CalledProcessError, e:
33
 
        hookenv.log(
34
 
            "ERROR: Charm configured credentials can't access endpoint. %s" %
35
 
            str(e),
36
 
            hookenv.ERROR)
37
 
        sys.exit(1)
38
 
    hookenv.log(
39
 
        "Validated charm configuration credentials have access to block "
40
 
        "storage service")
41
 
 
42
 
 
43
 
def get_volume_attachments(volume_id):
44
 
    """Return a C{list} of volume attachments if present"""
45
 
    from ast import literal_eval
46
 
    try:
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:
52
 
        return []
53
 
    return attachments
54
 
 
55
 
 
56
 
def volume_exists(volume_id):
57
 
    """Returns C{True} when C{volume_id} already exists"""
58
 
    try:
59
 
        subprocess.check_call(
60
 
            "nova volume-show %s" % volume_id, shell=True)
61
 
    except subprocess.CalledProcessError:
62
 
        return False
63
 
    return True
64
 
 
65
 
 
66
 
def get_volume_id(volume_designation=None, instance_id=None):
67
 
    """Return the nova volume id associated with this unit
68
 
 
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}.
72
 
    """
73
 
    token = volume_designation
74
 
    if token:
75
 
        # Get volume by name or volume-id
76
 
        # nova volume-show will error if multiple matches are found
77
 
        command = (
78
 
            "nova volume-show '%s' | grep ' id ' | awk '{ print $4 }'" % token)
79
 
    elif instance_id:
80
 
        command = "nova volume-list | grep %s | awk '{print $2}'" % instance_id
81
 
    else:
82
 
        # Find volume by unit name
83
 
        token = hookenv.remote_unit()
84
 
        command = "nova volume-list | grep %s | awk '{print $2}'" % token
85
 
 
86
 
    try:
87
 
        output = subprocess.check_output(command, shell=True)
88
 
    except subprocess.CalledProcessError, e:
89
 
        hookenv.log(
90
 
            "ERROR: Couldn't find nova volume id for %s. %s" % (token, str(e)),
91
 
            hookenv.ERROR)
92
 
        sys.exit(1)
93
 
 
94
 
    lines = output.strip().split("\n")
95
 
    if len(lines) > 1:
96
 
        hookenv.log(
97
 
            "Error: Multiple nova volumes labeled as associated with "
98
 
            "%s. Cannot get_volume_id." % token, hookenv.ERROR)
99
 
        sys.exit(1)
100
 
    if lines[0]:
101
 
        return lines[0]
102
 
    return None
103
 
 
104
 
 
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}.
110
 
    """
111
 
    if volume_designation is None:
112
 
        volume_designation = get_volume_id()
113
 
        if volume_designation is None:
114
 
            hookenv.log(
115
 
                "WARNING: Can't find volume_id to get status.",
116
 
                hookenv.WARNING)
117
 
            return None
118
 
    try:
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:
123
 
        hookenv.log(
124
 
            "Error: nova couldn't get status of volume %s. %s" %
125
 
            (volume_designation, str(e)), hookenv.ERROR)
126
 
        return None
127
 
    return output.strip()
128
 
 
129
 
 
130
 
def attach_nova_volume(instance_id, volume_id=None, size=None,
131
 
                       volume_label=None):
132
 
    """
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.
136
 
 
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.
140
 
    """
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
145
 
    if volume_id:
146
 
        if not volume_exists(volume_id):
147
 
            hookenv.log(
148
 
                "Requested volume-id (%s) does not exist. Unable to associate "
149
 
                "storage with %s" % (volume_id, remote_unit),
150
 
                hookenv.ERROR)
151
 
            sys.exit(1)
152
 
 
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":
160
 
            hookenv.log(
161
 
                "Cannot attach nova volume. Volume has unsupported status: %s"
162
 
                % status)
163
 
            sys.exit(1)
164
 
    else:
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)
168
 
        if not volume_id:
169
 
            if not size:
170
 
                size = hookenv.config("default_volume_size")
171
 
            hookenv.log(
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)
179
 
 
180
 
    device = None
181
 
    hookenv.log("Attaching %s (%s)" % (volume_label, volume_id))
182
 
    for x in range(10):
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)
191
 
            break
192
 
        else:
193
 
            sleep(5)
194
 
 
195
 
    if not device:
196
 
        hookenv.log(
197
 
            "ERROR: Unable to discover device attached by nova volume-attach",
198
 
            hookenv.ERROR)
199
 
        sys.exit(1)
200
 
    return device.strip()
201
 
 
202
 
 
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)
207
 
    if volume_id:
208
 
        status = get_volume_status(volume_id)
209
 
    else:
210
 
        hookenv.log("Cannot find volume name to detach, done")
211
 
        return
212
 
 
213
 
    if status == "available":
214
 
        hookenv.log("Volume (%s) already detached. Done" % volume_id)
215
 
        return
216
 
 
217
 
    hookenv.log(
218
 
        "Detaching volume (%s) from instance %s" % (volume_id, instance_id))
219
 
    try:
220
 
        subprocess.check_call(
221
 
            "nova volume-detach %s %s" % (instance_id, volume_id), shell=True)
222
 
    except subprocess.CalledProcessError, e:
223
 
        hookenv.log(
224
 
            "ERROR: Couldn't detach nova volume %s. %s" % (volume_id, str(e)),
225
 
            hookenv.ERROR)
226
 
        sys.exit(1)
227
 
    return