~julian-edwards/maas/clickjacking-bug-1298784

« back to all changes in this revision

Viewing changes to src/provisioningserver/import_images/boot_resources.py

  • Committer: MaaS Lander
  • Author(s): jtv at canonical
  • Date: 2014-04-28 09:33:58 UTC
  • mfrom: (2291.1.3 split-out-repowriter)
  • Revision ID: maas_lander-20140428093358-2rv11jn3odtikqyh
[r=gmb][bug=][author=jtv] Split the RepoWriter code out of its module, narrowing down its API so we can better understand and work with the code.  Document many unknown things, and rename mysterious variables and functions to reflect improved understanding.  We're hoping to make substantial changes to how we use the data coming out of Simplestreams, so we must understand what it is and how it works.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
    ]
19
19
 
20
20
from argparse import ArgumentParser
21
 
from datetime import datetime
22
21
import errno
23
 
from gzip import GzipFile
24
22
import os
25
23
from textwrap import dedent
26
24
 
30
28
from provisioningserver.import_images.download_descriptions import (
31
29
    download_all_image_descriptions,
32
30
    )
33
 
from provisioningserver.import_images.helpers import (
34
 
    get_signing_policy,
35
 
    logger,
 
31
from provisioningserver.import_images.download_resources import (
 
32
    download_all_boot_resources,
36
33
    )
37
 
from provisioningserver.import_images.product_mapping import ProductMapping
 
34
from provisioningserver.import_images.helpers import logger
 
35
from provisioningserver.import_images.product_mapping import map_products
38
36
from provisioningserver.utils import (
39
37
    atomic_write,
40
38
    call_and_check,
41
39
    locate_config,
42
40
    read_text_file,
43
41
    )
44
 
from simplestreams.contentsource import FdContentSource
45
 
from simplestreams.mirrors import (
46
 
    BasicMirrorWriter,
47
 
    UrlMirrorReader,
48
 
    )
49
 
from simplestreams.objectstores import FileStore
50
 
from simplestreams.util import (
51
 
    item_checksums,
52
 
    path_from_mirror_url,
53
 
    products_exdata,
54
 
    )
55
42
 
56
43
 
57
44
class NoConfigFile(Exception):
58
45
    """Raised when the config file for the script doesn't exist."""
59
46
 
60
47
 
61
 
def boot_reverse(boot_images_dict):
62
 
    """Determine the subarches supported by each boot resource.
63
 
 
64
 
    Many subarches may be deployed by a single boot resource.  We note only
65
 
    subarchitectures here and ignore architectures because the metadata format
66
 
    tightly couples a boot resource to its architecture.
67
 
 
68
 
    We can figure out for which architecture we need to use a specific boot
69
 
    resource by looking at its description in the metadata.  We can't do the
70
 
    same with subarch, because we may want to use a boot resource only for a
71
 
    specific subset of subarches.
72
 
 
73
 
    This function returns the relationship between boot resources and
74
 
    subarchitectures as a `ProductMapping`.
75
 
 
76
 
    :param boot: A `BootImageMapping` containing the images' metadata.
77
 
    :return: A `ProductMapping` mapping products to subarchitectures.
78
 
    """
79
 
    reverse = ProductMapping()
80
 
    for image, boot_resource in boot_images_dict.items():
81
 
        reverse.add(boot_resource, image.subarch)
82
 
    return reverse
83
 
 
84
 
 
85
48
def tgt_entry(arch, subarch, release, label, image):
86
49
    """Generate tgt target used to commission arch/subarch with release
87
50
 
116
79
    return entry
117
80
 
118
81
 
119
 
class RepoWriter(BasicMirrorWriter):
120
 
    """Download boot resources from an upstream Simplestreams repo.
121
 
 
122
 
    :ivar root_path:
123
 
    :ivar cache_path:
124
 
    :ivar product_dict: A `ProductMapping` describing the desired boot
125
 
        resources.
126
 
    """
127
 
 
128
 
    def __init__(self, root_path, cache_path, info):
129
 
        self._root_path = os.path.abspath(root_path)
130
 
        self.product_dict = info
131
 
        # XXX jtv 2014-04-11: FileStore now also takes an argument called
132
 
        # complete_callback, which can be used for progress reporting.
133
 
        self._cache = FileStore(os.path.abspath(cache_path))
134
 
        super(RepoWriter, self).__init__()
135
 
 
136
 
    def write(self, path, keyring=None):
137
 
        (mirror, rpath) = path_from_mirror_url(path, None)
138
 
        policy = get_signing_policy(rpath, keyring)
139
 
        reader = UrlMirrorReader(mirror, policy=policy)
140
 
        super(RepoWriter, self).sync(reader, rpath)
141
 
 
142
 
    def load_products(self, path=None, content_id=None):
143
 
        return
144
 
 
145
 
    def filter_version(self, data, src, target, pedigree):
146
 
        return self.product_dict.contains(products_exdata(src, pedigree))
147
 
 
148
 
    def insert_file(self, name, tag, checksums, size, contentsource):
149
 
        logger.info("Inserting file %s (tag=%s, size=%s).", name, tag, size)
150
 
        self._cache.insert(
151
 
            tag, contentsource, checksums, mutable=False, size=size)
152
 
        return [(self._cache._fullpath(tag), name)]
153
 
 
154
 
    def insert_root_image(self, tag, checksums, size, contentsource):
155
 
        root_image_tag = 'root-image-%s' % tag
156
 
        root_image_path = self._cache._fullpath(root_image_tag)
157
 
        root_tgz_tag = 'root-tgz-%s' % tag
158
 
        root_tgz_path = self._cache._fullpath(root_tgz_tag)
159
 
        if not os.path.isfile(root_image_path):
160
 
            logger.info("New root image: %s.", root_image_path)
161
 
            self._cache.insert(
162
 
                tag, contentsource, checksums, mutable=False, size=size)
163
 
            uncompressed = FdContentSource(
164
 
                GzipFile(self._cache._fullpath(tag)))
165
 
            self._cache.insert(root_image_tag, uncompressed, mutable=False)
166
 
            self._cache.remove(tag)
167
 
        if not os.path.isfile(root_tgz_path):
168
 
            logger.info("Converting root tarball: %s.", root_tgz_path)
169
 
            call_uec2roottar(root_image_path, root_tgz_path)
170
 
        return [(root_image_path, 'root-image'), (root_tgz_path, 'root-tgz')]
171
 
 
172
 
    def insert_item(self, data, src, target, pedigree, contentsource):
173
 
        item = products_exdata(src, pedigree)
174
 
        checksums = item_checksums(data)
175
 
        tag = checksums['sha256']
176
 
        size = data['size']
177
 
        ftype = item['ftype']
178
 
        if ftype == 'root-image.gz':
179
 
            links = self.insert_root_image(tag, checksums, size, contentsource)
180
 
        else:
181
 
            links = self.insert_file(
182
 
                ftype, tag, checksums, size, contentsource)
183
 
        for subarch in self.product_dict.get(item):
184
 
            dst_folder = os.path.join(
185
 
                self._root_path, item['arch'], subarch, item['release'],
186
 
                item['label'])
187
 
            if not os.path.exists(dst_folder):
188
 
                os.makedirs(dst_folder)
189
 
            for src, link_name in links:
190
 
                link_path = os.path.join(dst_folder, link_name)
191
 
                if os.path.isfile(link_path):
192
 
                    os.remove(link_path)
193
 
                os.link(src, link_path)
194
 
 
195
 
 
196
82
def install_boot_loaders(destination):
197
83
    """Install the all the required file from each bootloader method.
198
84
    :param destination: Directory where the loaders should be stored.
201
87
        method.install_bootloader(destination)
202
88
 
203
89
 
204
 
def call_uec2roottar(root_image_path, root_tgz_path):
205
 
    """Invoke `uec2roottar` with the given arguments.
206
 
 
207
 
    Here only so tests can stub it out.
208
 
 
209
 
    :param root_image_path: Input file.
210
 
    :param root_tgz_path: Output file.
211
 
    """
212
 
    call_and_check([
213
 
        'sudo', '/usr/bin/uec2roottar',
214
 
        '--user=maas',
215
 
        root_image_path,
216
 
        root_tgz_path,
217
 
        ])
218
 
 
219
 
 
220
90
def make_arg_parser(doc):
221
91
    """Create an `argparse.ArgumentParser` for this script."""
222
92
 
232
102
def compose_targets_conf(snapshot_path):
233
103
    """Produce the contents of a snapshot's tgt conf file.
234
104
 
235
 
    :param snasphot_path: Filesystem path to a snapshot of boot images.
 
105
    :param snapshot_path: Filesystem path to a snapshot of current upstream
 
106
        boot resources.
236
107
    :return: Contents for a `targets.conf` file.
237
108
    :rtype: bytes
238
109
    """
267
138
        )
268
139
 
269
140
 
270
 
def compose_snapshot_path(storage):
271
 
    """Put together a path for a new snapshot.
272
 
 
273
 
    A snapshot is a directory in `storage` containing images.  The name
274
 
    contains the date in a sortable format.
275
 
    """
276
 
    snapshot_name = 'snapshot-%s' % datetime.now().strftime('%Y%m%d-%H%M%S')
277
 
    return os.path.join(storage, snapshot_name)
278
 
 
279
 
 
280
141
def update_current_symlink(storage, latest_snapshot):
281
142
    """Symlink `latest_snapshot` as the "current" snapshot."""
282
143
    symlink_path = os.path.join(storage, 'current')
327
188
        logger.warn("Can't import: no Simplestreams sources configured.")
328
189
        return
329
190
 
330
 
    boot = download_all_image_descriptions(config)
331
 
    if boot.is_empty():
 
191
    image_descriptions = download_all_image_descriptions(config)
 
192
    if image_descriptions.is_empty():
332
193
        logger.warn(
333
194
            "No boot resources found.  Check configuration and connectivity.")
334
195
        return
335
196
 
336
197
    storage = config['boot']['storage']
337
 
    meta_file_content = boot.dump_json()
 
198
    meta_file_content = image_descriptions.dump_json()
338
199
    if meta_contains(storage, meta_file_content):
339
200
        # The current maas.meta already contains the new config.  No need to
340
201
        # rewrite anything.
341
202
        return
342
203
 
343
 
    reverse_boot = boot_reverse(boot)
344
 
    snapshot_path = compose_snapshot_path(storage)
345
 
    cache_path = os.path.join(storage, 'cache')
 
204
    product_mapping = map_products(image_descriptions)
 
205
 
 
206
    snapshot_path = download_all_boot_resources(
 
207
        sources, storage, product_mapping)
346
208
    targets_conf = os.path.join(snapshot_path, 'maas.tgt')
347
 
    writer = RepoWriter(snapshot_path, cache_path, reverse_boot)
348
 
 
349
 
    for source in sources:
350
 
        writer.write(source['path'], source['keyring'])
351
209
 
352
210
    targets_conf_content = compose_targets_conf(snapshot_path)
353
211