~ubuntu-branches/ubuntu/raring/nova/raring-proposed

« back to all changes in this revision

Viewing changes to nova/virt/images.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Adam Gandelman, Chuck Short
  • Date: 2012-11-23 09:04:58 UTC
  • mfrom: (1.1.66)
  • Revision ID: package-import@ubuntu.com-20121123090458-91565o7aev1i1h71
Tags: 2013.1~g1-0ubuntu1
[ Adam Gandelman ]
* debian/control: Ensure novaclient is upgraded with nova,
  require python-keystoneclient >= 1:2.9.0. (LP: #1073289)
* debian/patches/{ubuntu/*, rbd-security.patch}: Dropped, applied
  upstream.
* debian/control: Add python-testtools to Build-Depends.

[ Chuck Short ]
* New upstream version.
* Refreshed debian/patches/avoid_setuptools_git_dependency.patch.
* debian/rules: FTBFS if missing binaries.
* debian/nova-scheudler.install: Add missing rabbit-queues and
  nova-rpc-zmq-receiver.
* Remove nova-volume since it doesnt exist anymore, transition to cinder-*.
* debian/rules: install apport hook in the right place.
* debian/patches/ubuntu-show-tests.patch: Display test failures.
* debian/control: Add depends on genisoimage
* debian/control: Suggest guestmount.
* debian/control: Suggest websockify. (LP: #1076442)
* debian/nova.conf: Disable nova-volume service.
* debian/control: Depend on xen-system-* rather than the hypervisor.
* debian/control, debian/mans/nova-conductor.8, debian/nova-conductor.init,
  debian/nova-conductor.install, debian/nova-conductor.logrotate
  debian/nova-conductor.manpages, debian/nova-conductor.postrm
  debian/nova-conductor.upstart.in: Add nova-conductor service.
* debian/control: Add python-fixtures as a build deps.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
"""
23
23
 
24
24
import os
 
25
import re
25
26
 
26
27
from nova import exception
27
 
from nova import flags
28
28
from nova.image import glance
29
29
from nova.openstack.common import cfg
30
30
from nova.openstack.common import log as logging
39
39
                help='Force backing images to raw format'),
40
40
]
41
41
 
42
 
FLAGS = flags.FLAGS
43
 
FLAGS.register_opts(image_opts)
 
42
CONF = cfg.CONF
 
43
CONF.register_opts(image_opts)
 
44
 
 
45
 
 
46
class QemuImgInfo(object):
 
47
    BACKING_FILE_RE = re.compile((r"^(.*?)\s*\(actual\s+path\s*:"
 
48
                                  r"\s+(.*?)\)\s*$"), re.I)
 
49
    TOP_LEVEL_RE = re.compile(r"^([\w\d\s\_\-]+):(.*)$")
 
50
    SIZE_RE = re.compile(r"\(\s*(\d+)\s+bytes\s*\)", re.I)
 
51
 
 
52
    def __init__(self, cmd_output):
 
53
        details = self._parse(cmd_output)
 
54
        self.image = details.get('image')
 
55
        self.backing_file = details.get('backing_file')
 
56
        self.file_format = details.get('file_format')
 
57
        self.virtual_size = details.get('virtual_size')
 
58
        self.cluster_size = details.get('cluster_size')
 
59
        self.disk_size = details.get('disk_size')
 
60
        self.snapshots = details.get('snapshot_list', [])
 
61
        self.encryption = details.get('encryption')
 
62
 
 
63
    def __str__(self):
 
64
        lines = [
 
65
            'image: %s' % self.image,
 
66
            'file_format: %s' % self.file_format,
 
67
            'virtual_size: %s' % self.virtual_size,
 
68
            'disk_size: %s' % self.disk_size,
 
69
            'cluster_size: %s' % self.cluster_size,
 
70
            'backing_file: %s' % self.backing_file,
 
71
        ]
 
72
        if self.snapshots:
 
73
            lines.append("snapshots: %s" % self.snapshots)
 
74
        return "\n".join(lines)
 
75
 
 
76
    def _canonicalize(self, field):
 
77
        # Standardize on underscores/lc/no dash and no spaces
 
78
        # since qemu seems to have mixed outputs here... and
 
79
        # this format allows for better integration with python
 
80
        # - ie for usage in kwargs and such...
 
81
        field = field.lower().strip()
 
82
        for c in (" ", "-"):
 
83
            field = field.replace(c, '_')
 
84
        return field
 
85
 
 
86
    def _extract_bytes(self, details):
 
87
        # Replace it with the byte amount
 
88
        real_size = self.SIZE_RE.search(details)
 
89
        if real_size:
 
90
            details = real_size.group(1)
 
91
        try:
 
92
            details = utils.to_bytes(details)
 
93
        except (TypeError, ValueError):
 
94
            pass
 
95
        return details
 
96
 
 
97
    def _extract_details(self, root_cmd, root_details, lines_after):
 
98
        consumed_lines = 0
 
99
        real_details = root_details
 
100
        if root_cmd == 'backing_file':
 
101
            # Replace it with the real backing file
 
102
            backing_match = self.BACKING_FILE_RE.match(root_details)
 
103
            if backing_match:
 
104
                real_details = backing_match.group(2).strip()
 
105
        elif root_cmd in ['virtual_size', 'cluster_size', 'disk_size']:
 
106
            # Replace it with the byte amount (if we can convert it)
 
107
            real_details = self._extract_bytes(root_details)
 
108
        elif root_cmd == 'file_format':
 
109
            real_details = real_details.strip().lower()
 
110
        elif root_cmd == 'snapshot_list':
 
111
            # Next line should be a header, starting with 'ID'
 
112
            if not lines_after or not lines_after[0].startswith("ID"):
 
113
                msg = _("Snapshot list encountered but no header found!")
 
114
                raise ValueError(msg)
 
115
            consumed_lines += 1
 
116
            possible_contents = lines_after[1:]
 
117
            real_details = []
 
118
            # This is the sprintf pattern we will try to match
 
119
            # "%-10s%-20s%7s%20s%15s"
 
120
            # ID TAG VM SIZE DATE VM CLOCK (current header)
 
121
            for line in possible_contents:
 
122
                line_pieces = line.split(None)
 
123
                if len(line_pieces) != 6:
 
124
                    break
 
125
                else:
 
126
                    # Check against this pattern occuring in the final position
 
127
                    # "%02d:%02d:%02d.%03d"
 
128
                    date_pieces = line_pieces[5].split(":")
 
129
                    if len(date_pieces) != 3:
 
130
                        break
 
131
                    real_details.append({
 
132
                        'id': line_pieces[0],
 
133
                        'tag': line_pieces[1],
 
134
                        'vm_size': line_pieces[2],
 
135
                        'date': line_pieces[3],
 
136
                        'vm_clock': line_pieces[4] + " " + line_pieces[5],
 
137
                    })
 
138
                    consumed_lines += 1
 
139
        return (real_details, consumed_lines)
 
140
 
 
141
    def _parse(self, cmd_output):
 
142
        # Analysis done of qemu-img.c to figure out what is going on here
 
143
        # Find all points start with some chars and then a ':' then a newline
 
144
        # and then handle the results of those 'top level' items in a separate
 
145
        # function.
 
146
        #
 
147
        # TODO(harlowja): newer versions might have a json output format
 
148
        #                 we should switch to that whenever possible.
 
149
        #                 see: http://bit.ly/XLJXDX
 
150
        if not cmd_output:
 
151
            cmd_output = ''
 
152
        contents = {}
 
153
        lines = cmd_output.splitlines()
 
154
        i = 0
 
155
        line_am = len(lines)
 
156
        while i < line_am:
 
157
            line = lines[i]
 
158
            if not line.strip():
 
159
                i += 1
 
160
                continue
 
161
            consumed_lines = 0
 
162
            top_level = self.TOP_LEVEL_RE.match(line)
 
163
            if top_level:
 
164
                root = self._canonicalize(top_level.group(1))
 
165
                if not root:
 
166
                    i += 1
 
167
                    continue
 
168
                root_details = top_level.group(2).strip()
 
169
                details, consumed_lines = self._extract_details(root,
 
170
                                                                root_details,
 
171
                                                                lines[i + 1:])
 
172
                contents[root] = details
 
173
            i += consumed_lines + 1
 
174
        return contents
44
175
 
45
176
 
46
177
def qemu_img_info(path):
47
 
    """Return a dict containing the parsed output from qemu-img info."""
48
 
 
 
178
    """Return a object containing the parsed output from qemu-img info."""
49
179
    out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
50
180
                             'qemu-img', 'info', path)
51
 
 
52
 
    # output of qemu-img is 'field: value'
53
 
    data = {}
54
 
    for line in out.splitlines():
55
 
        field, val = line.split(':', 1)
56
 
        if val[0] == " ":
57
 
            val = val[1:]
58
 
        data[field] = val
59
 
 
60
 
    return data
 
181
    return QemuImgInfo(out)
 
182
 
 
183
 
 
184
def convert_image(source, dest, out_format):
 
185
    """Convert image to other format"""
 
186
    cmd = ('qemu-img', 'convert', '-O', out_format, source, dest)
 
187
    utils.execute(*cmd)
61
188
 
62
189
 
63
190
def fetch(context, image_href, path, _user_id, _project_id):
79
206
    with utils.remove_path_on_error(path_tmp):
80
207
        data = qemu_img_info(path_tmp)
81
208
 
82
 
        fmt = data.get('file format')
 
209
        fmt = data.file_format
83
210
        if fmt is None:
84
211
            raise exception.ImageUnacceptable(
85
212
                reason=_("'qemu-img info' parsing failed."),
86
213
                image_id=image_href)
87
214
 
88
 
        backing_file = data.get('backing file')
 
215
        backing_file = data.backing_file
89
216
        if backing_file is not None:
90
217
            raise exception.ImageUnacceptable(image_id=image_href,
91
218
                reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
92
219
 
93
 
        if fmt != "raw" and FLAGS.force_raw_images:
 
220
        if fmt != "raw" and CONF.force_raw_images:
94
221
            staged = "%s.converted" % path
95
222
            LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
96
223
            with utils.remove_path_on_error(staged):
97
 
                utils.execute('qemu-img', 'convert', '-O', 'raw', path_tmp,
98
 
                              staged)
 
224
                convert_image(path_tmp, staged, 'raw')
99
225
 
100
226
                data = qemu_img_info(staged)
101
 
                if data.get('file format') != "raw":
 
227
                if data.file_format != "raw":
102
228
                    raise exception.ImageUnacceptable(image_id=image_href,
103
229
                        reason=_("Converted to raw, but format is now %s") %
104
 
                        data.get('file format'))
 
230
                        data.file_format)
105
231
 
106
232
                os.rename(staged, path)
107
233