merged trunk
This commit is contained in:
116
bin/instance-usage-audit
Executable file
116
bin/instance-usage-audit
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 Openstack, LLC.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Cron script to generate usage notifications for instances neither created
|
||||||
|
nor destroyed in a given time period.
|
||||||
|
|
||||||
|
Together with the notifications generated by compute on instance
|
||||||
|
create/delete/resize, over that ime period, this allows an external
|
||||||
|
system consuming usage notification feeds to calculate instance usage
|
||||||
|
for each tenant.
|
||||||
|
|
||||||
|
Time periods are specified like so:
|
||||||
|
<number>[mdy]
|
||||||
|
|
||||||
|
1m = previous month. If the script is run April 1, it will generate usages
|
||||||
|
for March 1 thry March 31.
|
||||||
|
3m = 3 previous months.
|
||||||
|
90d = previous 90 days.
|
||||||
|
1y = previous year. If run on Jan 1, it generates usages for
|
||||||
|
Jan 1 thru Dec 31 of the previous year.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import gettext
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||||
|
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||||
|
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||||
|
os.pardir,
|
||||||
|
os.pardir))
|
||||||
|
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
|
||||||
|
sys.path.insert(0, POSSIBLE_TOPDIR)
|
||||||
|
|
||||||
|
gettext.install('nova', unicode=1)
|
||||||
|
|
||||||
|
|
||||||
|
from nova import context
|
||||||
|
from nova import db
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import log as logging
|
||||||
|
from nova import utils
|
||||||
|
|
||||||
|
from nova.notifier import api as notifier_api
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_string('instance_usage_audit_period', '1m',
|
||||||
|
'time period to generate instance usages for.')
|
||||||
|
|
||||||
|
|
||||||
|
def time_period(period):
|
||||||
|
today = datetime.date.today()
|
||||||
|
unit = period[-1]
|
||||||
|
if unit not in 'mdy':
|
||||||
|
raise ValueError('Time period must be m, d, or y')
|
||||||
|
n = int(period[:-1])
|
||||||
|
if unit == 'm':
|
||||||
|
year = today.year - (n // 12)
|
||||||
|
n = n % 12
|
||||||
|
if n >= today.month:
|
||||||
|
year -= 1
|
||||||
|
month = 12 + (today.month - n)
|
||||||
|
else:
|
||||||
|
month = today.month - n
|
||||||
|
begin = datetime.datetime(day=1, month=month, year=year)
|
||||||
|
end = datetime.datetime(day=1, month=today.month, year=today.year)
|
||||||
|
|
||||||
|
elif unit == 'y':
|
||||||
|
begin = datetime.datetime(day=1, month=1, year=today.year - n)
|
||||||
|
end = datetime.datetime(day=1, month=1, year=today.year)
|
||||||
|
|
||||||
|
elif unit == 'd':
|
||||||
|
b = today - datetime.timedelta(days=n)
|
||||||
|
begin = datetime.datetime(day=b.day, month=b.month, year=b.year)
|
||||||
|
end = datetime.datetime(day=today.day,
|
||||||
|
month=today.month,
|
||||||
|
year=today.year)
|
||||||
|
|
||||||
|
return (begin, end)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
utils.default_flagfile()
|
||||||
|
flags.FLAGS(sys.argv)
|
||||||
|
logging.setup()
|
||||||
|
begin, end = time_period(FLAGS.instance_usage_audit_period)
|
||||||
|
print "Creating usages for %s until %s" % (str(begin), str(end))
|
||||||
|
instances = db.instance_get_active_by_window(context.get_admin_context(),
|
||||||
|
begin,
|
||||||
|
end)
|
||||||
|
print "%s instances" % len(instances)
|
||||||
|
for instance_ref in instances:
|
||||||
|
usage_info = utils.usage_from_instance(instance_ref,
|
||||||
|
audit_period_begining=str(begin),
|
||||||
|
audit_period_ending=str(end))
|
||||||
|
notifier_api.notify('compute.%s' % FLAGS.host,
|
||||||
|
'compute.instance.exists',
|
||||||
|
notifier_api.INFO,
|
||||||
|
usage_info)
|
||||||
@@ -23,8 +23,14 @@ Starts both the EC2 and OpenStack APIs in separate processes.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
|
||||||
|
sys.argv[0]), os.pardir, os.pardir))
|
||||||
|
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
|
||||||
|
sys.path.insert(0, possible_topdir)
|
||||||
|
|
||||||
import nova.service
|
import nova.service
|
||||||
import nova.utils
|
import nova.utils
|
||||||
|
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ class VmCommands(object):
|
|||||||
:param host: show all instance on specified host.
|
:param host: show all instance on specified host.
|
||||||
:param instance: show specificed instance.
|
:param instance: show specificed instance.
|
||||||
"""
|
"""
|
||||||
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
|
print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
|
||||||
" %-10s %-10s %-10s %-5s" % (
|
" %-10s %-10s %-10s %-5s" % (
|
||||||
_('instance'),
|
_('instance'),
|
||||||
_('node'),
|
_('node'),
|
||||||
@@ -666,14 +666,14 @@ class VmCommands(object):
|
|||||||
context.get_admin_context(), host)
|
context.get_admin_context(), host)
|
||||||
|
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
|
print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
|
||||||
" %-10s %-10s %-10s %-5d" % (
|
" %-10s %-10s %-10s %-5d" % (
|
||||||
instance['hostname'],
|
instance['hostname'],
|
||||||
instance['host'],
|
instance['host'],
|
||||||
instance['instance_type'],
|
instance['instance_type'].name,
|
||||||
instance['state_description'],
|
instance['state_description'],
|
||||||
instance['launched_at'],
|
instance['launched_at'],
|
||||||
instance['image_id'],
|
instance['image_ref'],
|
||||||
instance['kernel_id'],
|
instance['kernel_id'],
|
||||||
instance['ramdisk_id'],
|
instance['ramdisk_id'],
|
||||||
instance['project_id'],
|
instance['project_id'],
|
||||||
@@ -905,7 +905,7 @@ class InstanceTypeCommands(object):
|
|||||||
try:
|
try:
|
||||||
instance_types.create(name, memory, vcpus, local_gb,
|
instance_types.create(name, memory, vcpus, local_gb,
|
||||||
flavorid, swap, rxtx_quota, rxtx_cap)
|
flavorid, swap, rxtx_quota, rxtx_cap)
|
||||||
except exception.InvalidInput:
|
except exception.InvalidInput, e:
|
||||||
print "Must supply valid parameters to create instance_type"
|
print "Must supply valid parameters to create instance_type"
|
||||||
print e
|
print e
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -591,6 +591,14 @@ class GlobalRoleNotAllowed(NotAllowed):
|
|||||||
message = _("Unable to use global role %(role_id)s")
|
message = _("Unable to use global role %(role_id)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ImageRotationNotAllowed(NovaException):
|
||||||
|
message = _("Rotation is not allowed for snapshots")
|
||||||
|
|
||||||
|
|
||||||
|
class RotationRequiredForBackup(NovaException):
|
||||||
|
message = _("Rotation param is required for backup image_type")
|
||||||
|
|
||||||
|
|
||||||
#TODO(bcwaldon): EOL this exception!
|
#TODO(bcwaldon): EOL this exception!
|
||||||
class Duplicate(NovaException):
|
class Duplicate(NovaException):
|
||||||
pass
|
pass
|
||||||
|
|||||||
28
nova/notifier/test_notifier.py
Normal file
28
nova/notifier/test_notifier.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from nova import flags
|
||||||
|
from nova import log as logging
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
NOTIFICATIONS = []
|
||||||
|
|
||||||
|
|
||||||
|
def notify(message):
|
||||||
|
"""Test notifier, stores notifications in memory for unittests."""
|
||||||
|
NOTIFICATIONS.append(message)
|
||||||
@@ -275,6 +275,11 @@ class FanoutAdapterConsumer(AdapterConsumer):
|
|||||||
unique = uuid.uuid4().hex
|
unique = uuid.uuid4().hex
|
||||||
self.queue = '%s_fanout_%s' % (topic, unique)
|
self.queue = '%s_fanout_%s' % (topic, unique)
|
||||||
self.durable = False
|
self.durable = False
|
||||||
|
# Fanout creates unique queue names, so we should auto-remove
|
||||||
|
# them when done, so they're not left around on restart.
|
||||||
|
# Also, we're the only one that should be consuming. exclusive
|
||||||
|
# implies auto_delete, so we'll just set that..
|
||||||
|
self.exclusive = True
|
||||||
LOG.info(_('Created "%(exchange)s" fanout exchange '
|
LOG.info(_('Created "%(exchange)s" fanout exchange '
|
||||||
'with "%(key)s" routing key'),
|
'with "%(key)s" routing key'),
|
||||||
dict(exchange=self.exchange, key=self.routing_key))
|
dict(exchange=self.exchange, key=self.routing_key))
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from nova import log as logging
|
|||||||
from nova import rpc
|
from nova import rpc
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
from nova.notifier import test_notifier
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.tests.compute')
|
LOG = logging.getLogger('nova.tests.compute')
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
@@ -62,6 +63,7 @@ class ComputeTestCase(test.TestCase):
|
|||||||
super(ComputeTestCase, self).setUp()
|
super(ComputeTestCase, self).setUp()
|
||||||
self.flags(connection_type='fake',
|
self.flags(connection_type='fake',
|
||||||
stub_network=True,
|
stub_network=True,
|
||||||
|
notification_driver='nova.notifier.test_notifier',
|
||||||
network_manager='nova.network.manager.FlatManager')
|
network_manager='nova.network.manager.FlatManager')
|
||||||
self.compute = utils.import_object(FLAGS.compute_manager)
|
self.compute = utils.import_object(FLAGS.compute_manager)
|
||||||
self.compute_api = compute.API()
|
self.compute_api = compute.API()
|
||||||
@@ -69,6 +71,7 @@ class ComputeTestCase(test.TestCase):
|
|||||||
self.user = self.manager.create_user('fake', 'fake', 'fake')
|
self.user = self.manager.create_user('fake', 'fake', 'fake')
|
||||||
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
||||||
self.context = context.RequestContext('fake', 'fake', False)
|
self.context = context.RequestContext('fake', 'fake', False)
|
||||||
|
test_notifier.NOTIFICATIONS = []
|
||||||
|
|
||||||
def fake_show(meh, context, id):
|
def fake_show(meh, context, id):
|
||||||
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
|
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
|
||||||
@@ -326,6 +329,50 @@ class ComputeTestCase(test.TestCase):
|
|||||||
self.assert_(console)
|
self.assert_(console)
|
||||||
self.compute.terminate_instance(self.context, instance_id)
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
|
def test_run_instance_usage_notification(self):
|
||||||
|
"""Ensure run instance generates apropriate usage notification"""
|
||||||
|
instance_id = self._create_instance()
|
||||||
|
self.compute.run_instance(self.context, instance_id)
|
||||||
|
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
|
||||||
|
msg = test_notifier.NOTIFICATIONS[0]
|
||||||
|
self.assertEquals(msg['priority'], 'INFO')
|
||||||
|
self.assertEquals(msg['event_type'], 'compute.instance.create')
|
||||||
|
payload = msg['payload']
|
||||||
|
self.assertEquals(payload['tenant_id'], self.project.id)
|
||||||
|
self.assertEquals(payload['user_id'], self.user.id)
|
||||||
|
self.assertEquals(payload['instance_id'], instance_id)
|
||||||
|
self.assertEquals(payload['instance_type'], 'm1.tiny')
|
||||||
|
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
|
||||||
|
self.assertEquals(str(payload['instance_type_id']), str(type_id))
|
||||||
|
self.assertTrue('display_name' in payload)
|
||||||
|
self.assertTrue('created_at' in payload)
|
||||||
|
self.assertTrue('launched_at' in payload)
|
||||||
|
self.assertEquals(payload['image_ref'], '1')
|
||||||
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
|
def test_terminate_usage_notification(self):
|
||||||
|
"""Ensure terminate_instance generates apropriate usage notification"""
|
||||||
|
instance_id = self._create_instance()
|
||||||
|
self.compute.run_instance(self.context, instance_id)
|
||||||
|
test_notifier.NOTIFICATIONS = []
|
||||||
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
|
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
|
||||||
|
msg = test_notifier.NOTIFICATIONS[0]
|
||||||
|
self.assertEquals(msg['priority'], 'INFO')
|
||||||
|
self.assertEquals(msg['event_type'], 'compute.instance.delete')
|
||||||
|
payload = msg['payload']
|
||||||
|
self.assertEquals(payload['tenant_id'], self.project.id)
|
||||||
|
self.assertEquals(payload['user_id'], self.user.id)
|
||||||
|
self.assertEquals(payload['instance_id'], instance_id)
|
||||||
|
self.assertEquals(payload['instance_type'], 'm1.tiny')
|
||||||
|
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
|
||||||
|
self.assertEquals(str(payload['instance_type_id']), str(type_id))
|
||||||
|
self.assertTrue('display_name' in payload)
|
||||||
|
self.assertTrue('created_at' in payload)
|
||||||
|
self.assertTrue('launched_at' in payload)
|
||||||
|
self.assertEquals(payload['image_ref'], '1')
|
||||||
|
|
||||||
def test_run_instance_existing(self):
|
def test_run_instance_existing(self):
|
||||||
"""Ensure failure when running an instance that already exists"""
|
"""Ensure failure when running an instance that already exists"""
|
||||||
instance_id = self._create_instance()
|
instance_id = self._create_instance()
|
||||||
@@ -378,6 +425,36 @@ class ComputeTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.compute.terminate_instance(self.context, instance_id)
|
self.compute.terminate_instance(self.context, instance_id)
|
||||||
|
|
||||||
|
def test_resize_instance_notification(self):
|
||||||
|
"""Ensure notifications on instance migrate/resize"""
|
||||||
|
instance_id = self._create_instance()
|
||||||
|
context = self.context.elevated()
|
||||||
|
|
||||||
|
self.compute.run_instance(self.context, instance_id)
|
||||||
|
test_notifier.NOTIFICATIONS = []
|
||||||
|
|
||||||
|
db.instance_update(self.context, instance_id, {'host': 'foo'})
|
||||||
|
self.compute.prep_resize(context, instance_id, 1)
|
||||||
|
migration_ref = db.migration_get_by_instance_and_status(context,
|
||||||
|
instance_id, 'pre-migrating')
|
||||||
|
|
||||||
|
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
|
||||||
|
msg = test_notifier.NOTIFICATIONS[0]
|
||||||
|
self.assertEquals(msg['priority'], 'INFO')
|
||||||
|
self.assertEquals(msg['event_type'], 'compute.instance.resize.prep')
|
||||||
|
payload = msg['payload']
|
||||||
|
self.assertEquals(payload['tenant_id'], self.project.id)
|
||||||
|
self.assertEquals(payload['user_id'], self.user.id)
|
||||||
|
self.assertEquals(payload['instance_id'], instance_id)
|
||||||
|
self.assertEquals(payload['instance_type'], 'm1.tiny')
|
||||||
|
type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
|
||||||
|
self.assertEquals(str(payload['instance_type_id']), str(type_id))
|
||||||
|
self.assertTrue('display_name' in payload)
|
||||||
|
self.assertTrue('created_at' in payload)
|
||||||
|
self.assertTrue('launched_at' in payload)
|
||||||
|
self.assertEquals(payload['image_ref'], '1')
|
||||||
|
self.compute.terminate_instance(context, instance_id)
|
||||||
|
|
||||||
def test_resize_instance(self):
|
def test_resize_instance(self):
|
||||||
"""Ensure instance can be migrated/resized"""
|
"""Ensure instance can be migrated/resized"""
|
||||||
instance_id = self._create_instance()
|
instance_id = self._create_instance()
|
||||||
|
|||||||
@@ -274,6 +274,22 @@ EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
|
|||||||
'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
|
'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
|
||||||
|
|
||||||
|
|
||||||
|
def usage_from_instance(instance_ref, **kw):
|
||||||
|
usage_info = dict(
|
||||||
|
tenant_id=instance_ref['project_id'],
|
||||||
|
user_id=instance_ref['user_id'],
|
||||||
|
instance_id=instance_ref['id'],
|
||||||
|
instance_type=instance_ref['instance_type']['name'],
|
||||||
|
instance_type_id=instance_ref['instance_type_id'],
|
||||||
|
display_name=instance_ref['display_name'],
|
||||||
|
created_at=str(instance_ref['created_at']),
|
||||||
|
launched_at=str(instance_ref['launched_at']) \
|
||||||
|
if instance_ref['launched_at'] else '',
|
||||||
|
image_ref=instance_ref['image_ref'])
|
||||||
|
usage_info.update(kw)
|
||||||
|
return usage_info
|
||||||
|
|
||||||
|
|
||||||
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
|
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
|
||||||
"""Generate a random password from the supplied symbols.
|
"""Generate a random password from the supplied symbols.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ function usage {
|
|||||||
echo ""
|
echo ""
|
||||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||||
echo " -r, --recreate-db Recreate the test database."
|
echo " -r, --recreate-db Recreate the test database (deprecated, as this is now the default)."
|
||||||
|
echo " -n, --no-recreate-db Don't recreate the test database."
|
||||||
echo " -x, --stop Stop running tests after the first error or failure."
|
echo " -x, --stop Stop running tests after the first error or failure."
|
||||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||||
echo " -p, --pep8 Just run pep8"
|
echo " -p, --pep8 Just run pep8"
|
||||||
@@ -25,6 +26,7 @@ function process_option {
|
|||||||
-V|--virtual-env) let always_venv=1; let never_venv=0;;
|
-V|--virtual-env) let always_venv=1; let never_venv=0;;
|
||||||
-N|--no-virtual-env) let always_venv=0; let never_venv=1;;
|
-N|--no-virtual-env) let always_venv=0; let never_venv=1;;
|
||||||
-r|--recreate-db) let recreate_db=1;;
|
-r|--recreate-db) let recreate_db=1;;
|
||||||
|
-n|--no-recreate-db) let recreate_db=0;;
|
||||||
-f|--force) let force=1;;
|
-f|--force) let force=1;;
|
||||||
-p|--pep8) let just_pep8=1;;
|
-p|--pep8) let just_pep8=1;;
|
||||||
-*) noseopts="$noseopts $1";;
|
-*) noseopts="$noseopts $1";;
|
||||||
@@ -41,7 +43,7 @@ noseargs=
|
|||||||
noseopts=
|
noseopts=
|
||||||
wrapper=""
|
wrapper=""
|
||||||
just_pep8=0
|
just_pep8=0
|
||||||
recreate_db=0
|
recreate_db=1
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
process_option $arg
|
process_option $arg
|
||||||
|
|||||||
Reference in New Issue
Block a user