Skip to content

Instantly share code, notes, and snippets.

@herrfeder
Last active November 23, 2022 07:37
Show Gist options
  • Save herrfeder/f06908c73584457dfd4e470ed088a8e2 to your computer and use it in GitHub Desktop.
Save herrfeder/f06908c73584457dfd4e470ed088a8e2 to your computer and use it in GitHub Desktop.
After Openstack MariaDB got corrupted, I tried with these script to recover the still existing ibd Files with a sqldump from a fresh Openstack Kolla-Ansible Deploy

Problem

  • when upgrading a kolla-ansible powered OpenStack Environment from a single node mariadb to a Galera Cluster, the database gots somehow corrupted (I'm no database expert). All nodes were corrupted or not even running. (Yes, there wasn't a backup) This led finally to the challenge, to recover the database from the still existing ibd-Files
  • modern MySQL/MariaDB distributions with InnoDB does allow import of Tables from ibd-Files. It's recommended to also import the associated cfg-File of each table which doesn't exist in my case
    • without these cfg-Files an error will be raised Internal error: Drop all secondary indexes before importing table <tablename> when .cfg file is missing and this leads to my approach to seperate the primary index statements from the secondary indexes and add them after the Tablespace gets imported successfully

Approach

  1. Create SQL Dump of a working fresh opensstack mariadb and store it for later:
mysqldump -A --compact -f -u root -p > sql_dump.sql
  1. Import the environment of a fresh openstack mariadb (in my case Xena) into a location like /var/lib/mysql
  2. Remove all created openstack relevant stuff in the database:
rm -r ibdata1 ib_* barbican/ cinder/ glance/ grafana/ heat/ keystone/ magnum/ neutron/ nova/ nova_api/ nova_cell0/ octavia/ placement/
  1. Run a mariadb container which is pointing to the location with the DB files and start mysqld inside
  • (I used https://github.com/nexdrew/rekcod to get the runtime for the kolla container and adapted it to not shutdown on failing db start)
  • (maybe you have to change the ownership of the /var/lib/mysql folder)
docker run --rm --name mariadb_back -p 3306:3307 --runtime runc -v /etc/kolla/mariadb/:/var/lib/kolla/config_files/:ro -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v /var/lib/mysql:/var/lib/mysql:rw -v kolla_logs:/var/log/kolla/:rw --net host -h masternode -l build-date='20221019' -l kolla_version='13.6.1' -l maintainer='Kolla Project (https://launchpad.net/kolla)' -l name='mariadb-server' -e 'KOLLA_CONFIG_STRATEGY=COPY_ALWAYS' -e 'KOLLA_SERVICE_NAME=mariadb' -e 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' -e 'LANG=en_US.UTF-8' -e 'KOLLA_BASE_DISTRO=ubuntu' -e 'KOLLA_DISTRO_PYTHON_VERSION=3.8' -e 'KOLLA_BASE_ARCH=x86_64' -e 'PS1=$(tput bold)($(printenv KOLLA_SERVICE_NAME))$(tput sgr0)[$(id -un)@$(hostname -s) $(pwd)]$ ' -e 'DEBIAN_FRONTEND=noninteractive' -e 'PIP_INDEX_URL=' -e 'PIP_TRUSTED_HOST=' -e 'PIP_EXTRA_INDEX_URL=' --entrypoint /bin/bash -it  quay.io/openstack.kolla/ubuntu-source-mariadb-server:xena
  1. Inject the first part of the dump to create the tables: (should work without issues)
  • filter only the CREATE TABLE statements without secondary indexes using my script:
    • python3 filter_mariadb_openstack_sql.py sql_dump.sql create > openstack_create_filter.sql
  • insert the result into the databse:
    • mysql -u root -p -f -vvv < openstack_create_filter.sql
  1. Discard the tablespaces: (should work without issues)
  • get the discard statements using my script:
    • python3 filter_mariadb_openstack_sql.py sql_dump.sql discard > openstack_discard_filter.sql
  • insert the result into the databse:
    • mysql -u root -p -f -vvv < openstack_discard_filter.sql
  1. Now try to import the *.idb files: (fails for me because of incompatible FSP_SPACE_FLAGS)
  • Copy the *.idb files from the database you want to rescue into seperate folders in the database location:
    • for i in $(cat ~/working_dumps/openstack_databases); do cp ~/mariadb_backup/${i}/*.ibd ${i}/; done
    • chown -R 42434:42434 /var/lib/mysql/*
  • filter the import statements from the sql dump:
    • python3 filter_mariadb_openstack_sql.py sql_dump.sql import > openstack_import_filter.sql
  • now try to import the tablespaces: mysql -u root -p -f -vvv < openstack_import_filter.sql
  1. If this suceeds you can add the remaining secondary indexes:
  • filter only the KEYS, UNIQUE KEYS and CONSTRAINTs using my script:
    • python3 filter_mariadb_openstack_sql.py sql_dump.sql alter > openstack_alter_filter.sql
  • apply these statements to the database: mysql -u root -p -f -vvv < openstack_alter_filter.sql

Result

ERROR 1808 (HY000) at line 744: Schema mismatch (Expected FSP_SPACE_FLAGS=0x15, .ibd file contains 0x21.) 
--------------
ALTER TABLE traits IMPORT TABLESPACE
--------------

filter_mariadb_openstack_sql.py

#!/usr/bin/env python3
# coding=UTF-8

import sys, re

regex_find_end_create = "ENGINE=InnoDB"
regex_ex_create_no_keys_string = "^(\s*INSERT INTO.*|\s*CONSTRAINT.*|\s*KEY.*|\s*UNIQUE.*)"
regex_ex_keys_string = "^(\s*CONSTRAINT.*|\s*KEY.*|\s*UNIQUE.*)"

regex_ex_use_db = "\s*USE.*"
regex_ex_create_table = "\s*CREATE TABLE.*"


filename = sys.argv[1]
command = sys.argv[2]

def open_file(filename):
    with open(sys.argv[1], encoding="utf8", errors='ignore') as f:
        content = f.read()
        content_split = content.split("\n")
        return content_split


def remove_regex(sql_content, regex_string):
    compiled_regex = re.compile(regex_string)
    create_end = re.compile(regex_find_end_create)
    result_arr = []
    for line in sql_content:
        result = []
        result = compiled_regex.sub('',line)
        if result:
            result = create_end.sub(regex_find_end_create + " ROW_FORMAT=redundant",result)
            result_arr.append(result)

    return result_arr


# dont know how to capsulate this in a elegant way :)
def extract_alter(sql_content, tablespace_action="IMPORT", add_keys=True, add_tablespace=True):
    regex_cr_tab = re.compile(regex_ex_create_table)
    regex_use_db = re.compile(regex_ex_use_db)
    regex_alt_key = re.compile(regex_ex_keys_string)
    alter_table_key_tmp = "ALTER TABLE {tablename} ADD {sql_command};"
    alter_table_tablespace_tmp = "ALTER TABLE {tablename} {action} TABLESPACE;"
    result_arr = []
    for line in sql_content:
        use = regex_use_db.match(line)
        if use:
            result_arr.append(use.string)
        create_table = regex_cr_tab.match(line)
        if create_table:
            table_name = create_table.string.split(" ")[2].strip("`")
            if add_tablespace:
                full_tablespace_sql = alter_table_tablespace_tmp.format(tablename=table_name, action=tablespace_action)
                result_arr.append(full_tablespace_sql)
        key = regex_alt_key.match(line)
        if add_keys: 
            if key:
                sql_command = key.string.strip(",")
                full_key_sql = alter_table_key_tmp.format(tablename=table_name,sql_command=sql_command)
                result_arr.append(full_key_sql)

    return result_arr



def remove_trailing_commas(sql_content):
    result_arr = []
    for index, line in enumerate(sql_content): 
        result = []
        result = line
        try:
            if sql_content[index + 1].startswith(") ENGINE"):
                result = line.rstrip(",") 
        except:
            pass
        if result:
            result_arr.append(result)

    return result_arr

def print_content(sql_content):
    print("\n".join(sql_content))


if __name__ == "__main__":

    content_split = open_file(filename)
    if command == "create":
        proc_content = remove_regex(content_split, regex_ex_create_no_keys_string)
        proc_content = remove_trailing_commas(proc_content)
        print_content(proc_content)
    elif command == "discard":
        proc_content = extract_alter(content_split, tablespace_action="DISCARD", add_keys=False)
        print_content(proc_content)
    elif command == "import":
        proc_content = extract_alter(content_split, tablespace_action="IMPORT", add_keys=False)
        print_content(proc_content)
    elif command == "alter":
        proc_content = extract_alter(content_split, add_tablespace=False)
        print_content(proc_content)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment