Move common utility functions to Utils class

Common utility functions that were defined in base.py are moved to their
own Utils class. Additionally this patch adds some checks to ensure keys
exist in setup() before attempting to access them.

Change-Id: Ib940eefce140e3552f41ff0e32123ae90fe81fe4
This commit is contained in:
Corey Bryant 2017-04-03 19:11:59 +00:00
parent 6898cb6c95
commit 110b773d98
4 changed files with 149 additions and 113 deletions

View File

@ -22,22 +22,11 @@ import yaml
from oslo_concurrency import lockutils
from snap_openstack.renderer import SnapFileRenderer
from snap_openstack.utils import SnapUtils
LOG = logging.getLogger(__name__)
SNAP_ENV = ['SNAP_NAME',
'SNAP_VERSION',
'SNAP_REVISION',
'SNAP_ARCH',
'SNAP_LIBRARY_PATH',
'SNAP',
'SNAP_DATA',
'SNAP_COMMON',
'SNAP_USER_DATA',
'SNAP_USER_COMMON',
'TMPDIR']
DEFAULT_EP_TYPE = 'simple'
UWSGI_EP_TYPE = 'uwsgi'
NGINX_EP_TYPE = 'nginx'
@ -52,48 +41,23 @@ DEFAULT_NGINX_ARGS = ["-g",
"daemon on; master_process on;"]
def snap_env():
'''Grab SNAP* environment variables
@return dict of all SNAP* environment variables indexed in lower case
'''
_env = {}
for key in SNAP_ENV:
_env[key.lower()] = os.environ.get(key)
return _env
def ensure_dir(filepath):
'''Ensure a directory exists
Ensure that the directory structure to support
the provided filepath exists.
@param filepath: string container full path to a file
'''
dir_name = os.path.dirname(filepath)
if not os.path.exists(dir_name):
LOG.info('Creating directory {}'.format(dir_name))
os.makedirs(dir_name, 0o750)
class OpenStackSnap(object):
'''Main executor class for snap-openstack'''
def __init__(self, config_file):
with open(config_file, 'r') as config:
self.configuration = yaml.load(config)
self.snap_env = snap_env()
@lockutils.synchronized('setup.lock', external=True,
lock_path="/var/lock/snap-openstack")
def setup(self):
'''Perform any pre-execution snap setup
Run this method prior to use of the execute metho
Run this method prior to use of the execute method.
'''
setup = self.configuration['setup']
renderer = SnapFileRenderer()
utils = SnapUtils()
LOG.debug(setup)
install = setup['install']
@ -106,38 +70,40 @@ class OpenStackSnap(object):
LOG.error(_msg)
raise ValueError(_msg)
for directory in setup['dirs']:
directory = os.path.join(root_dir, directory)
dir_name = directory.format(**self.snap_env)
LOG.debug('Ensuring directory {} exists'.format(dir_name))
if not os.path.exists(dir_name):
LOG.debug('Creating directory {}'.format(dir_name))
os.makedirs(dir_name, 0o750)
if 'dirs' in setup.keys():
for directory in setup['dirs']:
directory = os.path.join(root_dir, directory)
dir_name = directory.format(**utils.snap_env)
utils.ensure_dir(dir_name)
for template in setup['templates']:
target = setup['templates'][template]
target = os.path.join(root_dir, target)
target_file = target.format(**self.snap_env)
ensure_dir(target_file)
LOG.debug('Rendering {} to {}'.format(template, target_file))
with open(target_file, 'w') as tf:
os.fchmod(tf.fileno(), 0o640)
tf.write(renderer.render(template, self.snap_env))
if 'templates' in setup.keys():
for template in setup['templates']:
target = setup['templates'][template]
target = os.path.join(root_dir, target)
target_file = target.format(**utils.snap_env)
utils.ensure_dir(target_file, is_file=True)
LOG.debug('Rendering {} to {}'.format(template, target_file))
with open(target_file, 'w') as tf:
os.fchmod(tf.fileno(), 0o640)
tf.write(renderer.render(template, utils.snap_env))
for source, target in setup['copyfiles'].items():
source_dir = source.format(**self.snap_env)
dest = os.path.join(root_dir, target)
dest_dir = dest.format(**self.snap_env)
for source_name in os.listdir(source_dir):
s_file = os.path.join(source_dir, source_name)
d_file = os.path.join(dest_dir, source_name)
if not os.path.isfile(s_file) or os.path.exists(d_file):
continue
LOG.debug('Copying file {} to {}'.format(s_file, d_file))
shutil.copy2(s_file, d_file)
if 'copyfiles' in setup.keys():
for source, target in setup['copyfiles'].items():
source_dir = source.format(**utils.snap_env)
dest = os.path.join(root_dir, target)
dest_dir = dest.format(**utils.snap_env)
for source_name in os.listdir(source_dir):
s_file = os.path.join(source_dir, source_name)
d_file = os.path.join(dest_dir, source_name)
if not os.path.isfile(s_file) or os.path.exists(d_file):
continue
LOG.debug('Copying file {} to {}'.format(s_file, d_file))
shutil.copy2(s_file, d_file)
def execute(self, argv):
'''Execute snap command building out configuration and log options'''
utils = SnapUtils()
entry_point = self.configuration['entry_points'].get(argv[1])
if not entry_point:
_msg = 'Unable to find entry point for {}'.format(argv[1])
@ -148,8 +114,7 @@ class OpenStackSnap(object):
LOG.debug(entry_point)
# Build out command to run
cmd_type = entry_point.get('type',
DEFAULT_EP_TYPE)
cmd_type = entry_point.get('type', DEFAULT_EP_TYPE)
if cmd_type not in VALID_EP_TYPES:
_msg = 'Invalid entry point type: {}'.format(cmd_type)
@ -159,7 +124,7 @@ class OpenStackSnap(object):
if cmd_type == DEFAULT_EP_TYPE:
cmd = [entry_point['binary']]
for cfile in entry_point.get('config-files', []):
cfile = cfile.format(**self.snap_env)
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
cmd.append('--config-file={}'.format(cfile))
else:
@ -167,7 +132,7 @@ class OpenStackSnap(object):
', skipping'.format(cfile))
for cdir in entry_point.get('config-dirs', []):
cdir = cdir.format(**self.snap_env)
cdir = cdir.format(**utils.snap_env)
if os.path.exists(cdir):
cmd.append('--config-dir={}'.format(cdir))
else:
@ -176,7 +141,7 @@ class OpenStackSnap(object):
log_file = entry_point.get('log-file')
if log_file:
log_file = log_file.format(**self.snap_env)
log_file = log_file.format(**utils.snap_env)
cmd.append('--log-file={}'.format(log_file))
# Ensure any arguments passed to wrapper are propagated
@ -188,12 +153,12 @@ class OpenStackSnap(object):
uwsgi_dir = entry_point.get('uwsgi-dir')
if uwsgi_dir:
uwsgi_dir = uwsgi_dir.format(**self.snap_env)
uwsgi_dir = uwsgi_dir.format(**utils.snap_env)
cmd.append(uwsgi_dir)
log_file = entry_point.get('log-file')
if log_file:
log_file = log_file.format(**self.snap_env)
log_file = log_file.format(**utils.snap_env)
cmd.extend(['--logto', log_file])
elif cmd_type == NGINX_EP_TYPE:
@ -202,7 +167,7 @@ class OpenStackSnap(object):
cfile = entry_point.get('config-file')
if cfile:
cfile = cfile.format(**self.snap_env)
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
cmd.extend(['-c', '{}'.format(cfile)])
else:

View File

@ -3,30 +3,28 @@ setup:
dirs:
- "{snap_common}/etc/nova.conf.d"
- "{snap_common}/etc/nova"
- "{snap_common}/logs"
- "{snap_common}/log"
templates:
"nova-snap.conf.j2": "{snap_common}/etc/nova.conf.d/nova-snap.conf"
entry_points:
nova-manage:
binary: nova-manage
config-files:
- "{snap}/etc/nova/nova.conf"
- "{snap_common}/etc/nova/nova.conf"
- "/etc/nova/nova.conf"
config-dirs:
- "{snap_common}/etc/nova.conf.d"
- "/etc/nova/conf.d"
nova-scheduler:
type: simple
binary: nova-scheduler
config-files:
- "{snap}/etc/nova/nova.conf"
- "{snap_common}/etc/nova/nova.conf"
- "/etc/nova/nova.conf"
config-dirs:
- "{snap_common}/etc/nova.conf.d"
log-file: "{snap_common}/logs/nova-scheduler.log"
- "/etc/nova/conf.d"
log-file: "/var/log/nova/scheduler.log"
keystone-api:
type: uwsgi
uwsgi-dir: "{snap_common}/etc/uwsgi"
log-file: "{snap_common}/logs/keystone.log"
uwsgi-dir: "/etc/uwsgi"
log-file: "/var/log/uwsgi/keystone.log"
nova-broken:
type: unknown
binary: nova-broken

View File

@ -41,17 +41,20 @@ class TestOpenStackSnapExecute(test_base.TestCase):
def mock_exists(cls, path):
'''Test helper for os.path.exists'''
paths = {
'/snap/common/etc/nova/nova.conf': True,
'/var/snap/test/common/etc/nova.conf.d': True,
'/etc/nova/nova.conf': True,
'/etc/nova/conf.d': True,
}
return paths.get(path, False)
@patch.object(base, 'snap_env')
def mock_snap_utils(self, mock_utils):
snap_utils = mock_utils.return_value
snap_utils.snap_env.return_value = MOCK_SNAP_ENV
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')
def test_base_snap_config(self, mock_os,
mock_snap_env):
def test_base_snap_config(self, mock_os, mock_utils):
'''Ensure wrapped binary called with full args list'''
mock_snap_env.return_value = MOCK_SNAP_ENV
self.mock_snap_utils(mock_utils)
snap = base.OpenStackSnap(os.path.join(TEST_DIR,
'snap-openstack.yaml'))
mock_os.path.exists.side_effect = self.mock_exists
@ -60,17 +63,16 @@ class TestOpenStackSnapExecute(test_base.TestCase):
mock_os.execvp.assert_called_with(
'nova-scheduler',
['nova-scheduler',
'--config-file=/snap/common/etc/nova/nova.conf',
'--config-dir=/var/snap/test/common/etc/nova.conf.d',
'--log-file=/var/snap/test/common/logs/nova-scheduler.log']
'--config-file=/etc/nova/nova.conf',
'--config-dir=/etc/nova/conf.d',
'--log-file=/var/log/nova/scheduler.log']
)
@patch.object(base, 'snap_env')
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')
def test_base_snap_config_no_logging(self, mock_os,
mock_snap_env):
def test_base_snap_config_no_logging(self, mock_os, mock_utils):
'''Ensure wrapped binary called correctly with no logfile'''
mock_snap_env.return_value = MOCK_SNAP_ENV
self.mock_snap_utils(mock_utils)
snap = base.OpenStackSnap(os.path.join(TEST_DIR,
'snap-openstack.yaml'))
mock_os.path.exists.side_effect = self.mock_exists
@ -80,17 +82,16 @@ class TestOpenStackSnapExecute(test_base.TestCase):
mock_os.execvp.assert_called_with(
'nova-manage',
['nova-manage',
'--config-file=/snap/common/etc/nova/nova.conf',
'--config-dir=/var/snap/test/common/etc/nova.conf.d',
'--config-file=/etc/nova/nova.conf',
'--config-dir=/etc/nova/conf.d',
'db', 'sync']
)
@patch.object(base, 'snap_env')
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')
def test_base_snap_config_missing_entry_point(self, mock_os,
mock_snap_env):
def test_base_snap_config_missing_entry_point(self, mock_os, mock_utils):
'''Ensure ValueError raised for missing entry_point'''
mock_snap_env.return_value = MOCK_SNAP_ENV
self.mock_snap_utils(mock_utils)
snap = base.OpenStackSnap(os.path.join(TEST_DIR,
'snap-openstack.yaml'))
mock_os.path.exists.side_effect = self.mock_exists
@ -99,12 +100,11 @@ class TestOpenStackSnapExecute(test_base.TestCase):
['snap-openstack',
'nova-api'])
@patch.object(base, 'snap_env')
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')
def test_base_snap_config_uwsgi(self, mock_os,
mock_snap_env):
def test_base_snap_config_uwsgi(self, mock_os, mock_utils):
'''Ensure wrapped binary of uwsgi called with correct arguments'''
mock_snap_env.return_value = MOCK_SNAP_ENV
self.mock_snap_utils(mock_utils)
snap = base.OpenStackSnap(os.path.join(TEST_DIR,
'snap-openstack.yaml'))
mock_os.path.exists.side_effect = self.mock_exists
@ -114,16 +114,15 @@ class TestOpenStackSnapExecute(test_base.TestCase):
'uwsgi',
['uwsgi', '--master',
'--die-on-term', '--emperor',
'/var/snap/test/common/etc/uwsgi',
'--logto', '/var/snap/test/common/logs/keystone.log']
'/etc/uwsgi',
'--logto', '/var/log/uwsgi/keystone.log']
)
@patch.object(base, 'snap_env')
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')
def test_base_snap_config_invalid_ep_type(self, mock_os,
mock_snap_env):
def test_base_snap_config_invalid_ep_type(self, mock_os, mock_utils):
'''Ensure endpoint types are correctly validated'''
mock_snap_env.return_value = MOCK_SNAP_ENV
self.mock_snap_utils(mock_utils)
snap = base.OpenStackSnap(os.path.join(TEST_DIR,
'snap-openstack.yaml'))
mock_os.path.exists.side_effect = self.mock_exists

74
snap_openstack/utils.py Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
# Copyright 2016 Canonical UK Limited
#
# 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 logging
import os
LOG = logging.getLogger(__name__)
SNAP_ENV = ['SNAP_NAME',
'SNAP_VERSION',
'SNAP_REVISION',
'SNAP_ARCH',
'SNAP_LIBRARY_PATH',
'SNAP',
'SNAP_DATA',
'SNAP_COMMON',
'SNAP_USER_DATA',
'SNAP_USER_COMMON',
'TMPDIR']
class SnapUtils(object):
'''Class for common utilities'''
def __init__(self):
self._snap_env = self._collect_snap_env()
def _collect_snap_env(self):
'''Collect SNAP* environment variables
@return dict of all SNAP* environment variables indexed in lower case
'''
_env = {}
for key in SNAP_ENV:
_env[key.lower()] = os.environ.get(key)
LOG.info('Snap environment: {}'.format(_env))
return _env
@property
def snap_env(self):
'''Return SNAP* environment variables
@return dict of all SNAP* environment variables indexed in lower case
'''
return self._snap_env
def ensure_dir(self, path, is_file=False):
'''Ensure a directory exists
Ensure that the directory structure to support the provided file or
directory exists.
@param path: string containing full path to file or directory
@param is_file: true if directory name needs to be determined for file
'''
dir_name = path
if is_file:
dir_name = os.path.dirname(path)
if not os.path.exists(dir_name):
LOG.info('Creating directory {}'.format(dir_name))
os.makedirs(dir_name, 0o750)