Beispiel #1
0
def copy_image(session, headers, source_image_content):
    glance = chi.glance(session=session)
    extra = {
        k.lower().replace(f"{helpers.SWIFT_META_HEADER_PREFIX}", ""): v
        for k, v in headers.items()
        if k.lower().startswith(f"{helpers.SWIFT_META_HEADER_PREFIX}build")
    }

    tmp_image_name = f"img-cc-prod-{ulid.ulid()}"
    new_image = glance.images.create(
        name=tmp_image_name,
        visibility="private",
        disk_format=headers[f"{helpers.SWIFT_META_HEADER_PREFIX}disk-format"],
        container_format='bare',
        **extra)

    try:
        glance.images.upload(
            new_image['id'],
            io.BytesIO(source_image_content),
        )
    except Exception as e:
        # will raise exception if deleting fails; in this case, please
        # manually delete the empty image!
        glance.images.delete(new_image['id'])
        raise e

    return new_image
Beispiel #2
0
def image(request, keystone):
    image_arg = request.config.getoption('--image')
    if not image_arg:
        pytest.exit('--image argument is required.')

    glance = chi.glance(session=keystone)
    image = list(glance.images.list(filters={'name': image_arg}))
    if len(image) != 1:
        image = list(glance.images.list(filters={'id': image_arg}))
        if len(image) != 1:
            pytest.exit('No single image found with name or ID: "{}"'.format(
                image_arg))

    image = image[0]
    image_id = image['id']
    image_distro = image.get('build-distro',
                             request.config.getoption("--distro"))
    image_release = image.get('build-release',
                              request.config.getoption("--release"))
    image_variant = image.get('build-variant',
                              request.config.getoption("--variant"))

    if image_distro is None or image_release is None or image_variant is None:
        pytest.exit(
            'Image does not contain distro/release/variant in metadata. Cannot '
            'automatically infer test parameter; they must be '
            'manually specified.')

    return {
        'id': image_id,
        'distro': image_distro,
        'release': image_release,
        'variant': image_variant,
    }
Beispiel #3
0
def archive_image(auth_session, image, image_production_name):
    glance = chi.glance(session=auth_session)

    new_name = helpers.archival_name(image_production_name, image=image)

    logging.info(
        f"renaming image {image['name']} ({image['id']}) to {new_name}")
    glance.images.update(image['id'], name=new_name)
Beispiel #4
0
def do_upload(ip, rc, metadata, disk_format, **build_results):
    session = helpers.get_auth_session_from_rc(rc)
    glance = chi.glance(session=session)

    if disk_format != 'qcow2':
        converted_image = None
        if build_results['image_loc'].endswith('.qcow2'):
            converted_image = build_results['image_loc'][:-6] + '.img'
        else:
            converted_image = build_results['image_loc'] + '.img'
        out = helpers.remote_run(
            ip=ip,
            command='qemu-img convert -f qcow2 -O {} {} {}'.format(
                disk_format, build_results['image_loc'], converted_image))
        if out.failed:
            raise RuntimeError('converting image failed')
        build_results['image_loc'] = converted_image
        build_results['checksum'] = helpers.remote_run(
            ip=ip,
            command='md5sum {}'.format(converted_image)).split()[0].strip()

    image = glance.images.create(name='image-{}-{}-{}'.format(
        metadata['build-distro'], metadata['build-release'],
        metadata['build-tag']),
                                 disk_format=disk_format,
                                 container_format='bare',
                                 **metadata)

    upload_command = textwrap.dedent('''\
        curl -i -X PUT -H "X-Auth-Token: {token}" \
            -H "Content-Type: application/octet-stream" \
            -H "Connection: keep-alive" \
            -T "{filepath}" \
            {url}'''.format(
        token=session.get_token(),
        filepath=build_results['image_loc'],
        url=session.get_endpoint(service_type="image") +
        f"/v2/images/{image['id']}/file",
    ))
    out = helpers.remote_run(ip=ip, command=upload_command)

    image = glance.images.get(image["id"])

    if build_results['checksum'] != image['checksum']:
        raise RuntimeError('checksum mismatch! build: {} vs glance: {}'.format(
            repr(build_results['checksum']),
            repr(image['checksum']),
        ))

    return image
Beispiel #5
0
def get_image_by_name(auth_data, name):
    glance = chi.glance(session=helpers.get_auth_session_from_rc(auth_data))
    images = list(
        glance.images.list(filters={
            'name': name,
            'visibility': 'public'
        }))

    if len(images) > 1:
        raise ValueError(f"More than one {name} images found!")
    elif len(images) == 0:
        # first time deployment
        return None
    else:
        return images[0]
Beispiel #6
0
def archive_image(auth_session, owner, image):
    '''
    auth : Auth object
        Authentication/authorization object indicating site to target.
    image : str or dict
        ID of image to archive or dictionary with contents. New name autogenerated based on metadata.
    '''
    glance = chi.glance(session=auth_session)
    if isinstance(image, str):
        image = glance.images.get(image)

    new_name = helpers.archival_name(production_name(image=image), image=image)

    print('renaming image {} ({}) to {}'.format(image['name'], image['id'],
                                                new_name))
    glance.images.update(image['id'], name=new_name)
Beispiel #7
0
def main(argv=None):
    if argv is None:
        argv = sys.argv

    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument("--site-yaml",
                        type=str,
                        required=True,
                        help="A yaml file with site credentials.")
    parser.add_argument(
        '--latest',
        type=str,
        nargs=3,
        metavar=("distro", "release", "variant"),
        help=
        'Publish latest tested image given 3 args:<distro> <release> <variant>'
    )
    parser.add_argument('--image', type=str, help='Image id to publish')

    args = parser.parse_args(argv[1:])

    with open("/etc/chameleon_image_tools/supports.yaml", 'r') as f:
        supports = yaml.safe_load(f)

    auth_session = helpers.get_auth_session_from_yaml(args.site_yaml)

    release_images = []

    if args.image:
        headers, content = get_image_obj_by_id(args.image)
        if not headers or not content:
            raise RuntimeError(f"Image {args.image} found")
        release_images.append((headers, content))
    elif args.latest:
        distro, release, variant = args.latest
        release_images = get_latest_image_objs([(distro, release, variant)])
    else:
        # release all images
        identifiers = []
        for distro, dv in supports["supported_distros"].items():
            if "releases" not in dv:
                continue
            releases = dv["releases"]
            for release, rv in releases.items():
                if "variants" not in rv:
                    continue
                for variant in rv["variants"]:
                    identifiers.append((distro, release, variant))
        release_images = get_latest_image_objs(identifiers)

    glance = chi.glance(session=auth_session)
    for img in release_images:
        resp_headers = img[0]
        source_image_content = img[1]
        image_production_name = production_name(resp_headers, supports)

        # check if the latest image has been published
        latest_image = find_latest_published_image(glance, resp_headers,
                                                   image_production_name)
        timestamp_header = f"{helpers.SWIFT_META_HEADER_PREFIX}build-timestamp"
        revision_header = f"{helpers.SWIFT_META_HEADER_PREFIX}build-os-base-image-revision"
        if (latest_image and latest_image.get(
                "build-timestamp", None) == resp_headers[timestamp_header]
                and latest_image.get("build-os-base-image-revision",
                                     None) == resp_headers[revision_header]):
            d, r, v = get_identifiers(resp_headers)
            logging.info(
                f"The latest image {d}-{r}-{v} has been released. Nothing to do."
            )
            continue

        # publish image
        new_image = copy_image(auth_session, resp_headers,
                               source_image_content)

        # rename old image
        with open(args.site_yaml, 'r') as f:
            site = yaml.safe_load(f)
        named_images = list(
            glance.images.list(
                filters={
                    'name': image_production_name,
                    'owner': site["admin_project"],
                    'visibility': 'public'
                }))
        if len(named_images) == 1:
            archive_image(auth_session, named_images[0], image_production_name)
        elif len(named_images) > 1:
            raise RuntimeError('multiple images with the name "{}"'.format(
                image_production_name))
        elif len(named_images) < 1:
            # do nothing
            logging.info(
                f"no public production images {image_production_name} found on site"
            )

        # rename new image
        glance.images.update(
            new_image["id"],
            name=image_production_name,
            visibility="public",
        )
        logging.info(
            f"{image_production_name} has been published successfully!")
Beispiel #8
0
def copy_image(source_session, target_session, source_image_id):
    glance = chi.glance(session=source_session)
    source_image = glance.images.get(source_image_id)
    extra = extract_extra_properties(source_image)

    with tempfile.TemporaryDirectory() as tempdir:
        img_file = os.path.join(tempdir, 'image')

        glance_source = chi.glance(session=source_session)
        data = glance_source.images.data(source_image['id'])
        with open(img_file, "wb") as f:
            for chunk in data:
                f.write(chunk)

        glance_target = chi.glance(session=target_session)
        disk_format = source_image['disk_format']
        new_image = glance_target.images.create(
            name=source_image['name'],
            visibility=source_image['visibility'],
            disk_format=disk_format,
            container_format='bare',
            **extra)

        try:
            glance_target.images.upload(
                new_image['id'],
                open(img_file, "rb"),
                backend=helpers.CENTRALIZED_STORE,
            )
        except Exception as e:
            # will raise exception if deleting fails; in this case, please
            # manually delete the empty image!
            glance_target.images.delete(new_image['id'])
            raise e

        new_image_full = glance_target.images.get(new_image['id'])
        if new_image_full['checksum'] != source_image['checksum']:
            # skip checksum check for kvm site
            raise RuntimeError('checksum mismatch')

        # add metadata to swift object
        swift_conn = helpers.connect_to_swift_with_admin(
            target_session, helpers.CENTRALIZED_STORE_REGION_NAME)
        try:
            meta_headers = {
                f"{helpers.SWIFT_META_HEADER_PREFIX}{k}": f"{new_image[k]}"
                for k in new_image.keys() if k.startswith("build")
            }
            meta_headers[
                f"{helpers.SWIFT_META_HEADER_PREFIX}disk-format"] = new_image[
                    "disk_format"]
            existing_headers = swift_conn.head_object(
                helpers.CENTRALIZED_CONTAINER_NAME, new_image['id'])
            for header in KEEP_SWIFT_HEADERS:
                meta_headers[header] = existing_headers.get(header, None)

            swift_conn.put_object(
                container=helpers.CENTRALIZED_CONTAINER_NAME,
                obj=new_image['id'],
                headers=meta_headers,
                contents=None,
            )
        except Exception as e:
            glance_target.images.delete(new_image['id'])
            raise e

    return new_image_full
Beispiel #9
0
def main(argv=None):
    if argv is None:
        argv = sys.argv

    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument(
        'auth_jsons',
        type=str,
        help='File with auth info in JSON format for core sites.')
    parser.add_argument('--from-site',
                        type=str,
                        default="tacc",
                        help='Staging image located site')
    parser.add_argument(
        '--latest',
        type=str,
        nargs=3,
        metavar=('distro', 'release', 'variant'),
        help=
        'Publish latest tested image given 3 args:<distro> <release> <variant>'
    )
    parser.add_argument('--image', type=str, help='Image id to publish')
    parser.add_argument('--notify',
                        type=str,
                        nargs=2,
                        help="Send notifications to emails (comma-separated)"
                        " using relay: <relay> <emails>")

    args = parser.parse_args(argv[1:])

    with open(args.auth_jsons) as f:
        auth_data = json.load(f)

    auth_sessions = {}
    for site, auth_info in auth_data['auths'].items():
        if site in helpers.CHAMELEON_CORE_SITES:
            auth_sessions[site] = helpers.get_auth_session_from_rc(auth_info)

    centralized_auth_session = auth_sessions[helpers.CENTRALIZED_STORE_SITE]

    glance_source = chi.glance(session=auth_sessions[args.from_site])

    if args.image:
        source_image = glance_source.get(args.image)
        print('found specified image {} ({}) to publish'.format(
            source_image['name'], source_image['id']))
    elif args.latest:
        distro, release, variant = args.latest
        query = {
            'build-distro': distro,
            'build-release': release,
            'status': 'active',
            'build-variant': variant,
        }

        matching_images = list(glance_source.images.list(filters=query))
        matching_images.sort(reverse=True,
                             key=operator.itemgetter('created_at'))
        latest_image = next(iter(matching_images), None)
        if not latest_image:
            print(f"No latest image found with query {query}")
            return 0
        if latest_image.get("stores", None) == helpers.CENTRALIZED_STORE:
            print(
                f"The latest {distro}-{release} {variant} image has been released.",
                file=sys.stderr)
            return 0
        source_image = latest_image
        print('found latest image {} ({}) to publish'.format(
            source_image['name'], source_image['id']))
    else:
        print('must provide --latest or --image', file=sys.stderr)
        return 1

    image_production_name = production_name(image=source_image)

    # publish image
    new_image = copy_image(auth_sessions[args.from_site],
                           centralized_auth_session, source_image['id'])

    # rename old image at centralized object store
    glance = chi.glance(session=centralized_auth_session)
    named_images = list(
        glance.images.list(
            filters={
                'name': image_production_name,
                'owner': auth_data['auths'][args.from_site]['OS_PROJECT_ID'],
                'visibility': 'public',
                'stores': helpers.CENTRALIZED_STORE
            }))
    if len(named_images) == 1:
        archive_image(centralized_auth_session,
                      auth_data['auths'][args.from_site]['OS_PROJECT_ID'],
                      named_images[0]['id'])
    elif len(named_images) > 1:
        raise RuntimeError(
            'multiple images with the name "{}"'.format(image_production_name))
    elif len(named_images) < 1:
        # do nothing
        print(
            f"no public production images {image_production_name} found on site {helpers.CENTRALIZED_STORE_SITE}"
        )

    # rename new image at centralized object store
    glance = chi.glance(session=centralized_auth_session)
    glance.images.update(
        new_image['id'],
        name=image_production_name,
        visibility='public',
    )

    # delete tmp image
    print('delete tmp image {} from site {}'.format(source_image['id'],
                                                    args.from_site))
    glance = chi.glance(session=auth_sessions[args.from_site])
    glance.images.delete(source_image['id'])

    if args.notify:
        relay, to_emails = args.notify
        print(f"sending emails to {to_emails}")
        helpers.send_notification_mail(relay, "*****@*****.**",
                                       to_emails.split(","), new_image)
Beispiel #10
0
def main(argv=None):
    if argv is None:
        argv = sys.argv

    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument("--site-yaml",
                        type=str,
                        required=True,
                        help="A yaml file with site credentials.")
    parser.add_argument("--dry-run",
                        action='store_true',
                        help="Dry run; no actual hiding and deleting")

    args = parser.parse_args(argv[1:])

    with open("/etc/chameleon_image_tools/supports.yaml", 'r') as f:
        supports = yaml.safe_load(f)

    with open(args.site_yaml, 'r') as f:
        site_specs = yaml.safe_load(f)
    hide_image_age_in_month = site_specs["hide_image_age_in_month"]
    delete_image_age_in_month = site_specs["delete_image_age_in_month"]
    if hide_image_age_in_month >= delete_image_age_in_month:
        raise ValueError(
            "Parameter hide_image_age_in_month must be smaller than delete_image_age_in_month"
        )

    today = date.today()
    hide_datetime_cutoff = today + relativedelta(
        months=-hide_image_age_in_month)
    delete_datetime_cuttoff = today + relativedelta(
        months=-delete_image_age_in_month)

    auth_session = helpers.get_auth_session_from_yaml(args.site_yaml)
    glance = chi.glance(session=auth_session)
    nova = chi.nova(session=auth_session)

    ready_to_delete_images = []
    for distro, dv in supports["supported_distros"].items():
        if "releases" not in dv:
            continue
        releases = dv["releases"]
        for release, rv in releases.items():
            if "variants" not in rv:
                continue
            for variant in rv["variants"]:
                prod_name = production_name(supports, distro, release, variant)
                ready_to_delete_images.extend(
                    find_images(glance, nova, distro, release, variant,
                                prod_name))

    skip_images = []
    if args.dry_run:
        logging.info("It's dry-run. Print messages only.")
    if "skip_images" in site_specs:
        skip_images = site_specs["skip_images"]
    for img in ready_to_delete_images:
        if img["id"] in skip_images:
            logging.info(f"Skip image {img['name']} (id: {img['id']}).")
            continue
        create_date = datetime.strptime(img["created_at"],
                                        "%Y-%m-%dT%H:%M:%SZ").date()
        if create_date <= delete_datetime_cuttoff:
            try:
                if not args.dry_run:
                    glance.images.delete(img['id'])
                logging.info(
                    f"Image {img['name']} (id: {img['id']}) has been deleted.")
            except Exception:
                logging.exception(
                    f"Failed to delete {img['name']} (id: {img['id']}).")
        elif create_date <= hide_datetime_cutoff:
            try:
                if not args.dry_run:
                    glance.images.update(
                        img['id'],
                        visibility='private',
                    )
                logging.info(
                    f"Image {img['name']} (id: {img['id']}) has been hided.")
            except Exception:
                logging.exception(
                    f"Failed to hide {img['name']} (id: {img['id']}).")
Beispiel #11
0
 def glance(self):
     return chi.glance(session=self.session)