From 7155ad14c58b2d12c3608cde120c223ac9e1e5dc Mon Sep 17 00:00:00 2001 From: Jan-Eike Golenia Date: Wed, 12 Mar 2025 16:32:01 +0100 Subject: [PATCH] [SAP] Moved cinder-nanny from nanny repo to /cinder-nanny/scripts --- cinder-nanny/scripts/cinder-consistency.py | 1122 +++++++++++++++++ .../cinder-db-consistency-and-purge.sh | 57 + cinder-nanny/scripts/cinder-quota-sync.py | 309 +++++ cinder-nanny/scripts/cinder-quota-sync.sh | 51 + setup.cfg | 1 + 5 files changed, 1540 insertions(+) create mode 100644 cinder-nanny/scripts/cinder-consistency.py create mode 100755 cinder-nanny/scripts/cinder-db-consistency-and-purge.sh create mode 100755 cinder-nanny/scripts/cinder-quota-sync.py create mode 100755 cinder-nanny/scripts/cinder-quota-sync.sh diff --git a/cinder-nanny/scripts/cinder-consistency.py b/cinder-nanny/scripts/cinder-consistency.py new file mode 100644 index 00000000000..10cf395e2bf --- /dev/null +++ b/cinder-nanny/scripts/cinder-consistency.py @@ -0,0 +1,1122 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018 SAP SE +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# this script checks for volume attachments of already deleted volumes in the +# cinder db + +import argparse +import configparser +import datetime +import logging +import os +import sys + +from openstack import connection +from openstack import exceptions +from sqlalchemy import and_, MetaData, select, Table, create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + + +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format="%(asctime)-15s %(message)s") + + +# get all instances from nova +def get_nova_instances(conn): + nova_instances = dict() + + # get all instance from nova + try: + for nova_instance in conn.compute.servers( + details=False, all_projects=1 + ): + nova_instances[nova_instance.id] = nova_instance + if not nova_instances: + raise RuntimeError( + "- PLEASE CHECK MANUALLY - did not get any nova instances " + "from the nova api - this should in theory never happen ..." + ) + + except exceptions.HttpException as e: + log.warning( + "- PLEASE CHECK MANUALLY - got an http exception connecting" + "to openstack: %s", + str(e), + ) + sys.exit(1) + + except exceptions.SDKException as e: + log.warning( + "- PLEASE CHECK MANUALLY - got an sdk exception connecting" + "to openstack: %s", + str(e), + ) + sys.exit(1) + + # for i in nova_instances: + # print nova_instances[i].id + + if not nova_instances: + raise RuntimeError("Did not get any nova instances back.") + + return nova_instances + + +# get all volume attachments for volumes +def get_orphan_volume_attachments(meta): + orphan_volume_attachments = {} + orphan_volume_attachment_t = Table( + "volume_attachment", meta, autoload=True + ) + columns = [ + orphan_volume_attachment_t.c.id, + orphan_volume_attachment_t.c.instance_uuid, + ] + orphan_volume_attachment_q = select( + columns=columns, + whereclause=and_(orphan_volume_attachment_t.c.deleted == 0), + ) + + # return a dict indexed by orphan_volume_attachment_id and with the value + # nova_instance_uuid for non deleted orphan_volume_attachments + for ( + orphan_volume_attachment_id, + nova_instance_uuid, + ) in orphan_volume_attachment_q.execute(): + orphan_volume_attachments[orphan_volume_attachment_id] = ( + nova_instance_uuid + ) + + return orphan_volume_attachments + + +# get all the volume attachments in the cinder db for already deleted +# instances in nova +def get_wrong_orphan_volume_attachments( + nova_instances, orphan_volume_attachments +): + wrong_orphan_volume_attachments = {} + + for orphan_volume_attachment_id in orphan_volume_attachments: + if ( + nova_instances.get( + orphan_volume_attachments[orphan_volume_attachment_id] + ) + is None + ): + wrong_orphan_volume_attachments[orphan_volume_attachment_id] = ( + orphan_volume_attachments[orphan_volume_attachment_id] + ) + + return wrong_orphan_volume_attachments + + +# delete volume attachments in the cinder db for already deleted instances +# in nova +def fix_wrong_orphan_volume_attachments( + meta, wrong_orphan_volume_attachments, fix_limit +): + if len(wrong_orphan_volume_attachments) <= int(fix_limit): + orphan_volume_attachment_t = Table( + "volume_attachment", meta, autoload=True + ) + + for orphan_volume_attachment_id in wrong_orphan_volume_attachments: + log.info( + "-- action: deleting orphan volume attachment id: %s", + orphan_volume_attachment_id, + ) + now = datetime.datetime.utcnow() + delete_orphan_volume_attachment_q = ( + orphan_volume_attachment_t.update() + .where( + orphan_volume_attachment_t.c.id + == orphan_volume_attachment_id + ) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_orphan_volume_attachment_q.execute() + + else: + log.warning( + "- PLEASE CHECK MANUALLY - too many (more than %s) wrong orphan" + "volume attachments - denying to fix them automatically", + str(fix_limit), + ) + + +# get all the volumes in state "error_deleting" +def get_error_deleting_volumes(meta): + error_deleting_volumes = [] + + volumes_t = Table("volumes", meta, autoload=True) + error_deleting_volumes_q = select(columns=[volumes_t.c.id]).where( + and_(volumes_t.c.status == "error_deleting", volumes_t.c.deleted == 0) + ) + + # convert the query result into a list + for i in error_deleting_volumes_q.execute(): + error_deleting_volumes.append(i[0]) + + return error_deleting_volumes + + +# delete all the volumes in state "error_deleting" +def fix_error_deleting_volumes(meta, error_deleting_volumes): + volumes_t = Table("volumes", meta, autoload=True) + volume_attachment_t = Table("volume_attachment", meta, autoload=True) + volume_metadata_t = Table("volume_metadata", meta, autoload=True) + volume_admin_metadata_t = Table( + "volume_admin_metadata", meta, autoload=True + ) + + for error_deleting_volumes_id in error_deleting_volumes: + now = datetime.datetime.utcnow() + log.info( + "-- action: deleting possible volume admin metadata" + "for volume id: %s", + error_deleting_volumes_id, + ) + delete_volume_admin_metadata_q = ( + volume_admin_metadata_t.update() + .where( + volume_admin_metadata_t.c.volume_id + == error_deleting_volumes_id + ) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_admin_metadata_q.execute() + log.info( + "-- action: deleting possible volume metadata for volume id: %s", + error_deleting_volumes_id, + ) + delete_volume_metadata_q = ( + volume_metadata_t.update() + .where(volume_metadata_t.c.volume_id == error_deleting_volumes_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_metadata_q.execute() + log.info( + "-- action: deleting possible volume attachments" + " for volume id: %s", + error_deleting_volumes_id, + ) + delete_volume_attachment_q = ( + volume_attachment_t.update() + .where( + volume_attachment_t.c.volume_id == error_deleting_volumes_id + ) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_attachment_q.execute() + log.info( + "-- action: deleting volume id: %s", error_deleting_volumes_id + ) + delete_volume_q = ( + volumes_t.update() + .where(volumes_t.c.id == error_deleting_volumes_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_q.execute() + + +# get all the snapshots in state "error_deleting" +def get_error_deleting_snapshots(meta): + error_deleting_snapshots = [] + + snapshots_t = Table("snapshots", meta, autoload=True) + error_deleting_snapshots_q = select(columns=[snapshots_t.c.id]).where( + and_( + snapshots_t.c.status == "error_deleting", + snapshots_t.c.deleted == 0, + ) + ) + + # convert the query result into a list + for i in error_deleting_snapshots_q.execute(): + error_deleting_snapshots.append(i[0]) + + return error_deleting_snapshots + + +# delete all the snapshots in state "error_deleting" +def fix_error_deleting_snapshots(meta, error_deleting_snapshots): + snapshots_t = Table("snapshots", meta, autoload=True) + + for error_deleting_snapshots_id in error_deleting_snapshots: + log.info( + "-- action: deleting snapshot id: %s", error_deleting_snapshots_id + ) + now = datetime.datetime.utcnow() + delete_snapshot_q = ( + snapshots_t.update() + .where(snapshots_t.c.id == error_deleting_snapshots_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_snapshot_q.execute() + + +# get all the rows with a volume_admin_metadata still defined where the +# corresponding volume is already deleted +def get_wrong_volume_admin_metadata(meta): + wrong_admin_metadata = {} + volume_admin_metadata_t = Table( + "volume_admin_metadata", meta, autoload=True + ) + volumes_t = Table("volumes", meta, autoload=True) + admin_metadata_join = volume_admin_metadata_t.join( + volumes_t, volume_admin_metadata_t.c.volume_id == volumes_t.c.id + ) + columns = [ + volumes_t.c.id, + volumes_t.c.deleted, + volume_admin_metadata_t.c.id, + volume_admin_metadata_t.c.deleted, + ] + wrong_volume_admin_metadata_q = ( + select(columns=columns) + .select_from(admin_metadata_join) + .where( + and_( + volumes_t.c.deleted == 1, + volume_admin_metadata_t.c.deleted == 0, + ) + ) + ) + + # return a dict indexed by volume_admin_metadata_id and with the value + # volume_id for non deleted volume_admin_metadata + for ( + volume_id, + volume_deleted, + volume_admin_metadata_id, + volume_admin_metadata_deleted, + ) in wrong_volume_admin_metadata_q.execute(): + wrong_admin_metadata[volume_admin_metadata_id] = volume_id + return wrong_admin_metadata + + +# delete volume_admin_metadata still defined where the corresponding volume +# is already deleted +def fix_wrong_volume_admin_metadata(meta, wrong_admin_metadata): + volume_admin_metadata_t = Table( + "volume_admin_metadata", meta, autoload=True + ) + + for volume_admin_metadata_id in wrong_admin_metadata: + log.info( + "-- action: deleting volume_admin_metadata id: %s", + volume_admin_metadata_id, + ) + now = datetime.datetime.utcnow() + delete_volume_admin_metadata_q = ( + volume_admin_metadata_t.update() + .where(volume_admin_metadata_t.c.id == volume_admin_metadata_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_admin_metadata_q.execute() + + +# get all the rows with a volume_glance_metadata still defined where the +# corresponding volume is already deleted +def get_wrong_volume_glance_metadata_volumes(meta): + wrong_glance_metadata = {} + volume_glance_metadata_t = Table( + "volume_glance_metadata", meta, autoload=True + ) + volumes_t = Table("volumes", meta, autoload=True) + glance_metadata_join = volume_glance_metadata_t.join( + volumes_t, volume_glance_metadata_t.c.volume_id == volumes_t.c.id + ) + columns = [ + volumes_t.c.id, + volumes_t.c.deleted, + volume_glance_metadata_t.c.id, + volume_glance_metadata_t.c.deleted, + ] + wrong_volume_glance_metadata_q = ( + select(columns=columns) + .select_from(glance_metadata_join) + .where( + and_( + volumes_t.c.deleted == 1, + volume_glance_metadata_t.c.deleted == 0, + ) + ) + ) + + # return a dict indexed by volume_glance_metadata_id and with the + # value volume_id for non deleted volume_glance_metadata + for ( + volume_id, + volume_deleted, + volume_glance_metadata_id, + volume_glance_metadata_deleted, + ) in wrong_volume_glance_metadata_q.execute(): + wrong_glance_metadata[volume_glance_metadata_id] = volume_id + return wrong_glance_metadata + + +# delete volume_glance_metadata still defined where the corresponding volume +# is already deleted +def fix_wrong_volume_glance_metadata_volumes(meta, wrong_glance_metadata): + volume_glance_metadata_t = Table( + "volume_glance_metadata", meta, autoload=True + ) + + for volume_glance_metadata_id in wrong_glance_metadata: + log.info( + "-- action: deleting volume_glance_metadata id (volume): %s", + volume_glance_metadata_id, + ) + now = datetime.datetime.utcnow() + delete_volume_glance_metadata_q = ( + volume_glance_metadata_t.update() + .where(volume_glance_metadata_t.c.id == volume_glance_metadata_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_glance_metadata_q.execute() + + +# get all the rows with a volume_glance_metadata still defined where the +# corresponding snapshot is already deleted +def get_wrong_volume_glance_metadata_snapshots(meta): + wrong_glance_metadata = {} + volume_glance_metadata_t = Table( + "volume_glance_metadata", meta, autoload=True + ) + snapshots_t = Table("snapshots", meta, autoload=True) + glance_metadata_join = volume_glance_metadata_t.join( + snapshots_t, volume_glance_metadata_t.c.snapshot_id == snapshots_t.c.id + ) + columns = [ + snapshots_t.c.id, + snapshots_t.c.deleted, + volume_glance_metadata_t.c.id, + volume_glance_metadata_t.c.deleted, + ] + wrong_volume_glance_metadata_q = ( + select(columns=columns) + .select_from(glance_metadata_join) + .where( + and_( + snapshots_t.c.deleted == 1, + volume_glance_metadata_t.c.deleted == 0, + ) + ) + ) + + # return a dict indexed by volume_glance_metadata_id and with the value + # volume_id for non deleted volume_glance_metadata + for ( + snapshot_id, + snapshot_deleted, + volume_glance_metadata_id, + volume_glance_metadata_deleted, + ) in wrong_volume_glance_metadata_q.execute(): + wrong_glance_metadata[volume_glance_metadata_id] = snapshot_id + return wrong_glance_metadata + + +# delete volume_glance_metadata still defined where the corresponding volume +# is snapshot deleted +def fix_wrong_volume_glance_metadata_snapshots(meta, wrong_glance_metadata): + volume_glance_metadata_t = Table( + "volume_glance_metadata", meta, autoload=True + ) + + for volume_glance_metadata_id in wrong_glance_metadata: + log.info( + "-- action: deleting volume_glance_metadata id (snapshot): %s", + volume_glance_metadata_id, + ) + now = datetime.datetime.utcnow() + delete_volume_glance_metadata_q = ( + volume_glance_metadata_t.update() + .where(volume_glance_metadata_t.c.id == volume_glance_metadata_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_glance_metadata_q.execute() + + +# get all the rows with a volume_metadata still defined where the corresponding +# volume is already deleted +def get_wrong_volume_metadata(meta): + wrong_metadata = {} + volume_metadata_t = Table("volume_metadata", meta, autoload=True) + volumes_t = Table("volumes", meta, autoload=True) + metadata_join = volume_metadata_t.join( + volumes_t, volume_metadata_t.c.volume_id == volumes_t.c.id + ) + columns = [ + volumes_t.c.id, + volumes_t.c.deleted, + volume_metadata_t.c.id, + volume_metadata_t.c.deleted, + ] + wrong_volume_metadata_q = ( + select(columns=columns) + .select_from(metadata_join) + .where( + and_(volumes_t.c.deleted == 1, volume_metadata_t.c.deleted == 0) + ) + ) + + # return a dict indexed by volume_metadata_id and with the value volume_id + # for non deleted volume_metadata + for ( + volume_id, + volume_deleted, + volume_metadata_id, + volume_metadata_deleted, + ) in wrong_volume_metadata_q.execute(): + wrong_metadata[volume_metadata_id] = volume_id + return wrong_metadata + + +# delete volume_metadata still defined where the corresponding volume is +# already deleted +def fix_wrong_volume_metadata(meta, wrong_metadata): + volume_metadata_t = Table("volume_metadata", meta, autoload=True) + + for volume_metadata_id in wrong_metadata: + log.info( + "-- action: deleting volume_metadata id: %s", volume_metadata_id + ) + now = datetime.datetime.utcnow() + delete_volume_metadata_q = ( + volume_metadata_t.update() + .where(volume_metadata_t.c.id == volume_metadata_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_metadata_q.execute() + + +# get all the rows with a volume attachment still defined where the +# corresponding volume is already deleted +def get_wrong_volume_attachments(meta): + wrong_attachments = {} + volume_attachment_t = Table("volume_attachment", meta, autoload=True) + volumes_t = Table("volumes", meta, autoload=True) + attachment_join = volume_attachment_t.join( + volumes_t, volume_attachment_t.c.volume_id == volumes_t.c.id + ) + columns = [ + volumes_t.c.id, + volumes_t.c.deleted, + volume_attachment_t.c.id, + volume_attachment_t.c.deleted, + ] + wrong_volume_attachment_q = ( + select(columns=columns) + .select_from(attachment_join) + .where( + and_(volumes_t.c.deleted == 1, volume_attachment_t.c.deleted == 0) + ) + ) + + # return a dict indexed by volume_attachment_id and with the value + # volume_id for non deleted volume_attachments + for ( + volume_id, + volume_deleted, + volume_attachment_id, + volume_attachment_deleted, + ) in wrong_volume_attachment_q.execute(): + wrong_attachments[volume_attachment_id] = volume_id + return wrong_attachments + + +# delete volume attachment still defined where the corresponding volume is +# already deleted +def fix_wrong_volume_attachments(meta, wrong_attachments, fix_limit): + if len(wrong_attachments) <= int(fix_limit): + volume_attachment_t = Table("volume_attachment", meta, autoload=True) + + for volume_attachment_id in wrong_attachments: + log.info( + "-- action: deleting volume attachment id: %s", + volume_attachment_id, + ) + now = datetime.datetime.utcnow() + delete_volume_attachment_q = ( + volume_attachment_t.update() + .where(volume_attachment_t.c.id == volume_attachment_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_volume_attachment_q.execute() + + else: + log.warning( + "- PLEASE CHECK MANUALLY - too many (more than %s) wrong volume" + " attachments - denying to fix them automatically", + str(fix_limit), + ) + + +# get all the rows with a snapshot_metadata still defined where the +# corresponding snapshot is already deleted +def get_wrong_snapshot_metadata(meta): + wrong_metadata = {} + snapshot_metadata_t = Table("snapshot_metadata", meta, autoload=True) + snapshots_t = Table("snapshots", meta, autoload=True) + metadata_join = snapshot_metadata_t.join( + snapshots_t, snapshot_metadata_t.c.snapshot_id == snapshots_t.c.id + ) + columns = [ + snapshots_t.c.id, + snapshots_t.c.deleted, + snapshot_metadata_t.c.id, + snapshot_metadata_t.c.deleted, + ] + wrong_snapshot_metadata_q = ( + select(columns=columns) + .select_from(metadata_join) + .where( + and_( + snapshots_t.c.deleted == 1, snapshot_metadata_t.c.deleted == 0 + ) + ) + ) + + # return a dict indexed by snapshot_metadata_id and with the value + # snapshot_id for non deleted snapshot_metadata + for ( + snapshot_id, + snapshot_deleted, + snapshot_metadata_id, + snapshot_metadata_deleted, + ) in wrong_snapshot_metadata_q.execute(): + wrong_metadata[snapshot_metadata_id] = snapshot_id + return wrong_metadata + + +# delete snapshot_metadata still defined where the corresponding snapshot is +# already deleted +def fix_wrong_snapshot_metadata(meta, wrong_metadata): + snapshot_metadata_t = Table("snapshot_metadata", meta, autoload=True) + + for snapshot_metadata_id in wrong_metadata: + log.info( + "-- action: deleting snapshot_metadata id: %s", + snapshot_metadata_id, + ) + now = datetime.datetime.utcnow() + delete_snapshot_metadata_q = ( + snapshot_metadata_t.update() + .where(snapshot_metadata_t.c.id == snapshot_metadata_id) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_snapshot_metadata_q.execute() + + +# get all the rows with a group_volume_type_mapping still defined where the +# corresponding group_id is already deleted +def get_wrong_group_volume_type_mappings(meta): + wrong_group_volume_type_mappings = {} + group_volume_type_mapping_t = Table( + "group_volume_type_mapping", meta, autoload=True + ) + groups_t = Table("groups", meta, autoload=True) + group_volume_type_mapping_join = group_volume_type_mapping_t.join( + groups_t, group_volume_type_mapping_t.c.group_id == groups_t.c.id + ) + columns = [ + groups_t.c.id, + groups_t.c.deleted, + group_volume_type_mapping_t.c.id, + group_volume_type_mapping_t.c.deleted, + ] + wrong_group_volume_type_mapping_q = ( + select(columns=columns) + .select_from(group_volume_type_mapping_join) + .where( + and_( + groups_t.c.deleted == 1, + group_volume_type_mapping_t.c.deleted == 0, + ) + ) + ) + + # return a dict indexed by volume_attachment_id and with the value + # volume_id for non deleted volume_attachments + for ( + group_id, + group_deleted, + group_volume_type_mapping_id, + group_volume_type_mapping_deleted, + ) in wrong_group_volume_type_mapping_q.execute(): + wrong_group_volume_type_mappings[group_volume_type_mapping_id] = ( + group_id + ) + return wrong_group_volume_type_mappings + + +# delete group_volume_type_mapping still defined where the corresponding +# groupid is already deleted +def fix_wrong_group_volume_type_mappings( + meta, wrong_group_volume_type_mappings, fix_limit +): + if len(wrong_group_volume_type_mappings) <= int(fix_limit): + group_volume_type_mapping_t = Table( + "group_volume_type_mapping", meta, autoload=True + ) + + for group_volume_type_mapping_id in wrong_group_volume_type_mappings: + log.info( + "-- action: deleting group_volume_type_mapping id: %s", + group_volume_type_mapping_id, + ) + now = datetime.datetime.utcnow() + delete_group_volume_type_mapping_q = ( + group_volume_type_mapping_t.update() + .where( + group_volume_type_mapping_t.c.id + == group_volume_type_mapping_id + ) + .values(updated_at=now, deleted_at=now, deleted=1) + ) + delete_group_volume_type_mapping_q.execute() + + else: + log.warning( + "- PLEASE CHECK MANUALLY - too many (more than %s) wrong " + "group_volume_type_mappings - denying to fix them automatically", + str(fix_limit), + ) + + +# get all the rows, which have the deleted flag set, but not the delete_at +# column +def get_missing_deleted_at(meta, table_names): + missing_deleted_at = {} + for t in table_names: + a_table_t = Table(t, meta, autoload=True) + a_table_select_deleted_at_q = a_table_t.select().where( + and_(a_table_t.c.deleted == 1, a_table_t.c.deleted_at is None) + ) + + for row in a_table_select_deleted_at_q.execute(): + missing_deleted_at[row.id] = t + return missing_deleted_at + + +# set deleted_at to updated_at value if not set for marked as deleted rows +def fix_missing_deleted_at(meta, table_names): + now = datetime.datetime.utcnow() + for t in table_names: + a_table_t = Table(t, meta, autoload=True) + + log.info( + "- action: fixing columns with missing deleted_at times " + "in the %s table", + t, + ) + a_table_set_deleted_at_q = ( + a_table_t.update() + .where( + and_(a_table_t.c.deleted == 1, a_table_t.c.deleted_at is None) + ) + .values(deleted_at=now) + ) + a_table_set_deleted_at_q.execute() + + +# get all the rows with a service still defined where the corresponding +# volume is already deleted +def get_deleted_services_still_used_in_volumes(meta): + deleted_services_still_used_in_volumes = {} + services_t = Table("services", meta, autoload=True) + volumes_t = Table("volumes", meta, autoload=True) + services_volumes_join = services_t.join( + volumes_t, services_t.c.uuid == volumes_t.c.service_uuid + ) + columns = [ + services_t.c.uuid, + services_t.c.deleted, + volumes_t.c.id, + volumes_t.c.deleted, + ] + deleted_services_still_used_in_volumes_q = ( + select(columns=columns) + .select_from(services_volumes_join) + .where(and_(volumes_t.c.deleted == 0, services_t.c.deleted == 1)) + ) + + # return a dict indexed by service_uuid and with the value volume_id + # for deleted but still referenced services + for ( + service_uuid, + service_deleted, + volume_id, + volume_deleted, + ) in deleted_services_still_used_in_volumes_q.execute(): + deleted_services_still_used_in_volumes[service_uuid] = volume_id + return deleted_services_still_used_in_volumes + + +# delete services still defined where the corresponding volume is already +# deleted +def fix_deleted_services_still_used_in_volumes( + meta, deleted_services_still_used_in_volumes +): + services_t = Table("services", meta, autoload=True) + + for ( + deleted_services_still_used_in_volumes_id + ) in deleted_services_still_used_in_volumes: + log.info( + "-- action: undeleting service uuid: %s", + deleted_services_still_used_in_volumes_id, + ) + undelete_services_q = ( + services_t.update() + .where( + services_t.c.uuid == deleted_services_still_used_in_volumes_id + ) + .values(deleted=0, deleted_at=None) + ) + undelete_services_q.execute() + + +# establish an openstack connection +def makeOsConnection(): + try: + conn = connection.Connection( + auth_url=os.getenv("OS_AUTH_URL"), + project_name=os.getenv("OS_PROJECT_NAME"), + project_domain_name=os.getenv("OS_PROJECT_DOMAIN_NAME"), + username=os.getenv("OS_USERNAME"), + user_domain_name=os.getenv("OS_USER_DOMAIN_NAME"), + password=os.getenv("OS_PASSWORD"), + identity_api_version="3", + ) + except Exception as e: + log.warning( + "- PLEASE CHECK MANUALLY - problems connecting to openstack: %s", + str(e), + ) + sys.exit(1) + + return conn + + +# establish a database connection and return the handle +def makeConnection(db_url): + engine = create_engine(db_url) + engine.connect() + Session = sessionmaker(bind=engine) + thisSession = Session() + metadata = MetaData() + metadata.bind = engine + Base = declarative_base() + return thisSession, metadata, Base + + +# return the database connection string from the config file +def get_db_url(config_file): + parser = configparser.ConfigParser() + try: + parser.read(config_file) + db_url = parser.get("database", "connection", raw=True) + except Exception as e: + log.info("ERROR: Check Cinder configuration file - error %s", str(e)) + sys.exit(2) + return db_url + + +# cmdline handling +def parse_cmdline_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--config", default="./cinder.conf", help="configuration file" + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="print only what would be done without actually doing it", + ) + parser.add_argument( + "--fix-limit", + default=25, + help="maximum number of inconsistencies to fix automatically -" + " if there are more, automatic fixing is denied", + ) + return parser.parse_args() + + +def main(): # noqa: C901 - ignore for now, requires larger refactoring + try: + args = parse_cmdline_args() + except Exception as e: + log.error("Check command line arguments (%s)", e.strerror) + + # connect to openstack + conn = makeOsConnection() + + # connect to the DB + db_url = get_db_url(args.config) + cinder_session, cinder_metadata, cinder_Base = makeConnection(db_url) + + # fixing volume attachments at no longer existing instances + orphan_volume_attachments = get_orphan_volume_attachments(cinder_metadata) + nova_instances = get_nova_instances(conn) + wrong_orphan_volume_attachments = get_wrong_orphan_volume_attachments( + nova_instances, orphan_volume_attachments + ) + if len(wrong_orphan_volume_attachments) != 0: + log.info("- orphan volume attachments found:") + # print out what we would delete + for orphan_volume_attachment_id in wrong_orphan_volume_attachments: + log.info( + "-- orphan volume attachment (id in cinder db: %s) " + "for non existent instance in nova: %s", + orphan_volume_attachment_id, + orphan_volume_attachments[orphan_volume_attachment_id], + ) + if not args.dry_run: + log.info( + "- deleting orphan volume attachment inconsistencies found" + ) + fix_wrong_orphan_volume_attachments( + cinder_metadata, + wrong_orphan_volume_attachments, + args.fix_limit, + ) + else: + log.info("- no orphan volume attachments found") + + # fixing possible volumes in state "error-deleting" + error_deleting_volumes = get_error_deleting_volumes(cinder_metadata) + if len(error_deleting_volumes) != 0: + log.info("- volumes in state error_deleting found") + # print out what we would delete + for error_deleting_volumes_id in error_deleting_volumes: + log.info("-- volume id: %s", error_deleting_volumes_id) + if not args.dry_run: + log.info("- deleting volumes in state error_deleting") + fix_error_deleting_volumes(cinder_metadata, error_deleting_volumes) + else: + log.info("- no volumes in state error_deleting found") + + # fixing possible snapshots in state "error-deleting" + error_deleting_snapshots = get_error_deleting_snapshots(cinder_metadata) + if len(error_deleting_snapshots) != 0: + log.info("- snapshots in state error_deleting found") + # print out what we would delete + for error_deleting_snapshots_id in error_deleting_snapshots: + log.info("-- snapshot id: %s", error_deleting_snapshots_id) + if not args.dry_run: + log.info("- deleting snapshots in state error_deleting") + fix_error_deleting_snapshots( + cinder_metadata, error_deleting_snapshots + ) + else: + log.info("- no snapshots in state error_deleting found") + + # fixing possible wrong admin_metadata entries + wrong_admin_metadata = get_wrong_volume_admin_metadata(cinder_metadata) + if len(wrong_admin_metadata) != 0: + log.info("- volume_admin_metadata inconsistencies found") + # print out what we would delete + for volume_admin_metadata_id in wrong_admin_metadata: + log.info( + "-- volume_admin_metadata id: %s - deleted volume id: %s", + volume_admin_metadata_id, + wrong_admin_metadata[volume_admin_metadata_id], + ) + if not args.dry_run: + log.info("- removing volume_admin_metadata inconsistencies found") + fix_wrong_volume_admin_metadata( + cinder_metadata, wrong_admin_metadata + ) + else: + log.info("- volume_admin_metadata entries are consistent") + + # fixing possible wrong glance_metadata entries for volumes + wrong_glance_metadata = get_wrong_volume_glance_metadata_volumes( + cinder_metadata + ) + if len(wrong_glance_metadata) != 0: + log.info("- volume_glance_metadata inconsistencies for volumes found") + # print out what we would delete + for volume_glance_metadata_id in wrong_glance_metadata: + log.info( + "-- volume_glance_metadata id: %s - deleted volume id: %s", + volume_glance_metadata_id, + wrong_glance_metadata[volume_glance_metadata_id], + ) + if not args.dry_run: + log.info("- removing volume_glance_metadata inconsistencies found") + fix_wrong_volume_glance_metadata_volumes( + cinder_metadata, wrong_glance_metadata + ) + else: + log.info("- volume_glance_metadata entries for volumes are consistent") + + # fixing possible wrong glance_metadata entries for snapshots + wrong_glance_metadata = get_wrong_volume_glance_metadata_snapshots( + cinder_metadata + ) + if len(wrong_glance_metadata) != 0: + log.info( + "- volume_glance_metadata inconsistencies for snapshots found" + ) + # print out what we would delete + for volume_glance_metadata_id in wrong_glance_metadata: + log.info( + "-- volume_glance_metadata id: %s - deleted snapshot id: %s", + volume_glance_metadata_id, + wrong_glance_metadata[volume_glance_metadata_id], + ) + if not args.dry_run: + log.info("- removing volume_glance_metadata inconsistencies found") + fix_wrong_volume_glance_metadata_snapshots( + cinder_metadata, wrong_glance_metadata + ) + else: + log.info( + "- volume_glance_metadata entries for snapshots are consistent" + ) + + # fixing possible wrong volume metadata entries + wrong_metadata = get_wrong_volume_metadata(cinder_metadata) + if len(wrong_metadata) != 0: + log.info("- volume_metadata inconsistencies found") + # print out what we would delete + for volume_metadata_id in wrong_metadata: + log.info( + "-- volume_metadata id: %s - deleted volume id: %s", + volume_metadata_id, + wrong_metadata[volume_metadata_id], + ) + if not args.dry_run: + log.info("- removing volume_metadata inconsistencies found") + fix_wrong_volume_metadata(cinder_metadata, wrong_metadata) + else: + log.info("- volume_metadata entries are consistent") + + # fixing possible wrong attachment entries + wrong_attachments = get_wrong_volume_attachments(cinder_metadata) + if len(wrong_attachments) != 0: + log.info("- volume attachment inconsistencies found") + # print out what we would delete + for volume_attachment_id in wrong_attachments: + log.info( + "-- volume attachment id: %s - deleted volume id: %s", + volume_attachment_id, + wrong_attachments[volume_attachment_id], + ) + if not args.dry_run: + log.info("- removing volume attachment inconsistencies found") + fix_wrong_volume_attachments( + cinder_metadata, wrong_attachments, args.fix_limit + ) + else: + log.info("- volume attachments are consistent") + + # fixing possible wrong snapshot metadata entries + wrong_metadata = get_wrong_snapshot_metadata(cinder_metadata) + if len(wrong_metadata) != 0: + log.info("- snapshot_metadata inconsistencies found") + # print out what we would delete + for snapshot_metadata_id in wrong_metadata: + log.info( + "-- snapshot_metadata id: %s - deleted snapshot id: %s", + snapshot_metadata_id, + wrong_metadata[snapshot_metadata_id], + ) + if not args.dry_run: + log.info("- removing snapshot_metadata inconsistencies found") + fix_wrong_snapshot_metadata(cinder_metadata, wrong_metadata) + else: + log.info("- snapshot_metadata entries are consistent") + + # fixing possible wrong group_volume_type_mappings entries + wrong_group_volume_type_mappings = get_wrong_group_volume_type_mappings( + cinder_metadata + ) + if len(wrong_group_volume_type_mappings) != 0: + log.info("- group_volume_type_mappings inconsistencies found") + # print out what we would delete + for group_volume_type_mapping_id in wrong_group_volume_type_mappings: + log.info( + "-- group_volume_type_mapping id: %s - deleted group id: %s", + group_volume_type_mapping_id, + wrong_group_volume_type_mappings[group_volume_type_mapping_id], + ) + if not args.dry_run: + log.info( + "- removing group_volume_type_mapping inconsistencies found" + ) + fix_wrong_group_volume_type_mappings( + cinder_metadata, + wrong_group_volume_type_mappings, + args.fix_limit, + ) + else: + log.info("- group_volume_type_mappings are consistent") + + # fixing possible missing deleted_at timestamps in some tables + # tables which sometimes have missing deleted_at values + table_names = ["snapshots", "volume_attachment"] + + missing_deleted_at = get_missing_deleted_at(cinder_metadata, table_names) + if len(missing_deleted_at) != 0: + log.info("- missing deleted_at values found:") + # print out what we would delete + for missing_deleted_at_id in missing_deleted_at: + log.info( + "--- id %s of the %s table is missing deleted_at time", + missing_deleted_at_id, + missing_deleted_at[missing_deleted_at_id], + ) + if not args.dry_run: + log.info("- setting missing deleted_at values") + fix_missing_deleted_at(cinder_metadata, table_names) + else: + log.info("- no missing deleted_at values") + + deleted_services_still_used_in_volumes = ( + get_deleted_services_still_used_in_volumes(cinder_metadata) + ) + if len(deleted_services_still_used_in_volumes) != 0: + log.info("- deleted services still used in volumes found:") + # print out what we would delete + for ( + deleted_services_still_used_in_volumes_id + ) in deleted_services_still_used_in_volumes: + log.info( + "--- deleted service uuid %s still used in volumes" + " table entry %s", + deleted_services_still_used_in_volumes_id, + deleted_services_still_used_in_volumes[ + deleted_services_still_used_in_volumes_id + ], + ) + if not args.dry_run: + log.info("- undeleting service uuid still used in volumes table") + fix_deleted_services_still_used_in_volumes( + cinder_metadata, deleted_services_still_used_in_volumes + ) + else: + log.info("- deleted services still used in volumes") + + +if __name__ == "__main__": + main() diff --git a/cinder-nanny/scripts/cinder-db-consistency-and-purge.sh b/cinder-nanny/scripts/cinder-db-consistency-and-purge.sh new file mode 100755 index 00000000000..06510331b02 --- /dev/null +++ b/cinder-nanny/scripts/cinder-db-consistency-and-purge.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Copyright (c) 2018 SAP SE +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +set -e + +unset http_proxy https_proxy all_proxy no_proxy + +echo "INFO: copying cinder config files to /etc/cinder" +cp -vrL /cinder-etc/* /etc/cinder +# this is a temporary hack to avoid annoying raven warnings - we do not need sentry for this nanny for now +sed -i 's,raven\.handlers\.logging\.SentryHandler,logging.NullHandler,g' /etc/cinder/logging.ini + +DB_CONFIG="/etc/cinder/cinder.conf.d/secrets.conf" + +# cinder is now using proxysql by default in its config - change that back to a normal +# config for the nanny as we do not need it and do not have the proxy around by default +sed -i 's,@/cinder?unix_socket=/run/proxysql/mysql.sock&,@cinder-mariadb/cinder?,g' "${DB_CONFIG}" + +# we run an endless loop to run the script periodically +echo "INFO: starting the nanny job for the cinder db consistency check and purge" +if [ "$CINDER_CONSISTENCY_ENABLED" = "True" ] || [ "$CINDER_CONSISTENCY_ENABLED" = "true" ]; then + if [ "$CINDER_CONSISTENCY_DRY_RUN" = "False" ] || [ "$CINDER_CONSISTENCY_DRY_RUN" = "false" ]; then + if [ "$CINDER_CONSISTENCY_FIX_LIMIT" != "" ]; then + FIX_LIMIT="--fix-limit $CINDER_CONSISTENCY_FIX_LIMIT" + else + FIX_LIMIT="" + fi + echo -n "INFO: checking and fixing cinder db consistency - " + date + /var/lib/openstack/bin/python /var/lib/openstack/scripts/cinder-consistency.py --config "${DB_CONFIG}" $FIX_LIMIT + else + echo -n "INFO: checking cinder db consistency - " + date + /var/lib/openstack/bin/python /var/lib/openstack/scripts/cinder-consistency.py --config "${DB_CONFIG}" --dry-run + fi +fi +if [ "$CINDER_DB_PURGE_ENABLED" = "True" ] || [ "$CINDER_DB_PURGE_ENABLED" = "true" ]; then + echo -n "INFO: purging deleted cinder entities older than $CINDER_DB_PURGE_OLDER_THAN days from the cinder db - " + date + /var/lib/openstack/bin/cinder-manage db purge $CINDER_DB_PURGE_OLDER_THAN +fi +date diff --git a/cinder-nanny/scripts/cinder-quota-sync.py b/cinder-nanny/scripts/cinder-quota-sync.py new file mode 100755 index 00000000000..c12a1b128cf --- /dev/null +++ b/cinder-nanny/scripts/cinder-quota-sync.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python +# +# Copyright (c) 2017 CERN +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Author: Arne Wiebalck # noqa: H105 + +import argparse +import configparser +import datetime +import sys + +from prettytable import PrettyTable +from sqlalchemy import and_ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import func +from sqlalchemy import MetaData +from sqlalchemy.orm import sessionmaker +from sqlalchemy import select +from sqlalchemy.sql.expression import false +from sqlalchemy import Table + + +def get_projects(meta): + """Return a list of all projects in the database""" + + projects = [] + quota_usages_t = Table("quota_usages", meta, autoload=True) + quota_usages_q = select(columns=[quota_usages_t.c.project_id]).group_by( + quota_usages_t.c.project_id + ) + for project in quota_usages_q.execute(): + projects.append(project[0]) + + return projects + + +def yn_choice(): + """Return True/False after checking with the user""" + + yes = set(["yes", "y", "ye"]) + no = set(["no", "n"]) + + print("Do you want to sync? [Yes/No]") # noqa: C303 + while True: + choice = input().lower() + if choice in yes: + return True + elif choice in no: + return False + else: + sys.stdout.write("Do you want to sync? [Yes/No/Abort]") + + +def sync_quota_usages_project(meta, project_id, quota_usages_to_sync): + """Sync the quota usages of a project from real usages""" + + print(("Syncing %s", project_id)) # noqa: C303 + now = datetime.datetime.utcnow() + quota_usages_t = Table("quota_usages", meta, autoload=True) + for resource, quota in iter(list(quota_usages_to_sync.items())): + quota_usages_t.update().where( + and_( + quota_usages_t.c.project_id == project_id, + quota_usages_t.c.resource == resource, + ) + ).values(updated_at=now, in_use=quota).execute() + + +def get_snapshot_usages_project(meta, project_id): + """Return the snapshot resource usages of a project""" + + snapshots_t = Table("snapshots", meta, autoload=True) + snapshots_q = select( + columns=[ + snapshots_t.c.id, + snapshots_t.c.volume_size, + snapshots_t.c.volume_type_id, + ], + whereclause=and_( + snapshots_t.c.deleted == false(), + snapshots_t.c.project_id == project_id, + ), + ) + return snapshots_q.execute() + + +def get_volume_usages_project(meta, project_id): + """Return the volume resource usages of a project""" + + volumes_t = Table("volumes", meta, autoload=True) + volumes_q = select( + columns=[volumes_t.c.id, volumes_t.c.size, volumes_t.c.volume_type_id], + whereclause=and_( + volumes_t.c.deleted == false(), + volumes_t.c.project_id == project_id, + ), + ) + return volumes_q.execute() + + +def get_quota_usages_project(meta, project_id): + """Return the quota usages of a project""" + + quota_usages_t = Table("quota_usages", meta, autoload=True) + quota_usages_q = select( + columns=[quota_usages_t.c.resource, quota_usages_t.c.in_use], + whereclause=and_( + quota_usages_t.c.deleted == false(), + quota_usages_t.c.project_id == project_id, + ), + ) + return quota_usages_q.execute() + + +def get_resource_types(meta, project_id): + """Return a list of all resource types""" + + types = [] + quota_usages_t = Table("quota_usages", meta, autoload=True) + resource_types_q = select( + columns=[quota_usages_t.c.resource, func.count()], + whereclause=quota_usages_t.c.deleted == false(), + group_by=quota_usages_t.c.resource, + ) + for resource, _ in resource_types_q.execute(): + types.append(resource) + return types + + +def get_volume_types(meta, project_id): + """Return a dict with volume type id to name mapping""" + + types = {} + volume_types_t = Table("volume_types", meta, autoload=True) + volume_types_q = select( + columns=[volume_types_t.c.id, volume_types_t.c.name], + whereclause=volume_types_t.c.deleted == false(), + ) + for id, name in volume_types_q.execute(): + types[id] = name + return types + + +def makeConnection(db_url): + """Establish a database connection and return the handle""" + + engine = create_engine(db_url) + engine.connect() + Session = sessionmaker(bind=engine) + thisSession = Session() + metadata = MetaData() + metadata.bind = engine + Base = declarative_base() + tpl = thisSession, metadata, Base + return tpl + + +def get_db_url(config_file): + """Return the database connection string from the config file""" + + parser = configparser.ConfigParser() + try: + parser.read(config_file) + db_url = parser.get("database", "connection", raw=True) + except Exception: + print("ERROR: Check Cinder configuration file.") # noqa: C303 + sys.exit(2) + return db_url + + +def parse_cmdline_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--config", default="./cinder.conf", help="configuration file" + ) + parser.add_argument( + "--nosync", + action="store_true", + help="never sync resources (no interactive check)", + ) + parser.add_argument( + "--sync", + action="store_true", + help="always sync resources (no interactive check)", + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + "--list_projects", + action="store_true", + help="get a list of all projects in the database", + ) + group.add_argument("--project_id", type=str, help="project to check") + return parser.parse_args() + + +def main(): + try: + args = parse_cmdline_args() + except Exception as e: + sys.stdout.write("Check command line arguments (%s)" % e.strerror) + + # connect to the DB + db_url = get_db_url(args.config) + cinder_session, cinder_metadata, cinder_Base = makeConnection(db_url) + + # get the volume types + volume_types = get_volume_types(cinder_metadata, args.project_id) + + # get the resource types + resource_types = get_resource_types(cinder_metadata, args.project_id) + + # check/sync all projects found in the database + # + if args.list_projects: + for p in get_projects(cinder_metadata): + print(p) # noqa: C303 + sys.exit(0) + + # check a single project + # + print(("Checking " + args.project_id + " ...")) # noqa: C303 + + # get the quota usage of a project + quota_usages = {} + for resource, count in get_quota_usages_project( + cinder_metadata, args.project_id + ): + quota_usages[resource] = count + + # get the real usage of a project + real_usages = {} + for resource in resource_types: + real_usages[resource] = 0 + for _, size, type_id in get_volume_usages_project( + cinder_metadata, args.project_id + ): + real_usages["volumes"] += 1 + real_usages["volumes_" + volume_types[type_id]] += 1 + real_usages["gigabytes"] += size + real_usages["gigabytes_" + volume_types[type_id]] += size + for _, size, type_id in get_snapshot_usages_project( + cinder_metadata, args.project_id + ): + real_usages["snapshots"] += 1 + real_usages["snapshots_" + volume_types[type_id]] += 1 + real_usages["gigabytes"] += size + real_usages["gigabytes_" + volume_types[type_id]] += size + + # prepare the output + ptable = PrettyTable( + ["Project ID", "Resource", "Quota -> Real", "Sync Status"] + ) + + # find discrepancies between quota usage and real usage + quota_usages_to_sync = {} + for resource in resource_types: + try: + if real_usages[resource] != quota_usages[resource]: + quota_usages_to_sync[resource] = real_usages[resource] + ptable.add_row( + [ + args.project_id, + resource, + str(quota_usages[resource]) + + " -> " + + str(real_usages[resource]), + "\033[1m\033[91mMISMATCH\033[0m", + ] + ) + else: + ptable.add_row( + [ + args.project_id, + resource, + str(quota_usages[resource]) + + " -> " + + str(real_usages[resource]), + "\033[1m\033[92mOK\033[0m", + ] + ) + except KeyError: + pass + + if len(quota_usages): + print(ptable) # noqa: C303 + + # sync the quota with the real usage + if quota_usages_to_sync and not args.nosync and (args.sync or yn_choice()): + sync_quota_usages_project( + cinder_metadata, args.project_id, quota_usages_to_sync + ) + + +if __name__ == "__main__": + main() diff --git a/cinder-nanny/scripts/cinder-quota-sync.sh b/cinder-nanny/scripts/cinder-quota-sync.sh new file mode 100755 index 00000000000..03f6ad2303d --- /dev/null +++ b/cinder-nanny/scripts/cinder-quota-sync.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Copyright (c) 2018 SAP SE +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +set -e + +unset http_proxy https_proxy all_proxy no_proxy + +echo "INFO: copying cinder config files to /etc/cinder" +cp -vrL /cinder-etc/* /etc/cinder +# this is a temporary hack to avoid annoying raven warnings - we do not need sentry for this nanny for now +sed -i 's,raven\.handlers\.logging\.SentryHandler,logging.NullHandler,g' /etc/cinder/logging.ini + +DB_CONFIG="/etc/cinder/cinder.conf.d/secrets.conf" + +# cinder is now using proxysql by default in its config - change that back to a normal +# config for the nanny as we do not need it and do not have the proxy around by default +sed -i 's,@/cinder?unix_socket=/run/proxysql/mysql.sock&,@cinder-mariadb/cinder?,g' "${DB_CONFIG}" + +# we run an endless loop to run the script periodically +echo "INFO: starting the nanny jobs for the cinder db" +if [ "$CINDER_QUOTA_SYNC_ENABLED" = "True" ] || [ "$CINDER_QUOTA_SYNC_ENABLED" = "true" ]; then + echo -n "INFO: sync cinder quota - " + date + if [ "$CINDER_QUOTA_SYNC_DRY_RUN" = "False" ] || [ "$CINDER_QUOTA_SYNC_DRY_RUN" = "false" ]; then + SYNC_MODE="--sync" + else + SYNC_MODE="--nosync" + echo "INFO: running in dry-run mode only!" + fi + for i in `/var/lib/openstack/bin/python /var/lib/openstack/scripts/cinder-quota-sync.py --config "${DB_CONFIG}" --list_projects`; do + echo project: $i + /var/lib/openstack/bin/python /var/lib/openstack/scripts/cinder-quota-sync.py --config "${DB_CONFIG}" $SYNC_MODE --project_id $i + done +fi +echo -n "INFO: waiting $CINDER_NANNY_INTERVAL minutes before starting the next loop run - " +date diff --git a/setup.cfg b/setup.cfg index 5180138ae30..c795ffb84ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ data_files = etc/cinder/rootwrap.conf etc/cinder/resource_filters.json etc/cinder/rootwrap.d = etc/cinder/rootwrap.d/* + scripts/ = cinder-nanny/scripts/* packages = cinder