Adds support for:

* Use a Python virtualenv when deploying from source
* Default to shallow clone for git (depth=1)
* Pass http_proxy to pip installs if specified
This commit is contained in:
James Page 2015-06-10 15:01:59 +01:00
commit 77783a7d29
8 changed files with 129 additions and 33 deletions

View File

@ -53,9 +53,13 @@ from charmhelpers.contrib.network.ip import (
get_ipv6_addr get_ipv6_addr
) )
from charmhelpers.contrib.python.packages import (
pip_create_virtualenv,
pip_install,
)
from charmhelpers.core.host import lsb_release, mounts, umount from charmhelpers.core.host import lsb_release, mounts, umount
from charmhelpers.fetch import apt_install, apt_cache, install_remote from charmhelpers.fetch import apt_install, apt_cache, install_remote
from charmhelpers.contrib.python.packages import pip_install
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
@ -497,7 +501,17 @@ def git_install_requested():
requirements_dir = None requirements_dir = None
def git_clone_and_install(projects_yaml, core_project): def _git_yaml_load(projects_yaml):
"""
Load the specified yaml into a dictionary.
"""
if not projects_yaml:
return None
return yaml.load(projects_yaml)
def git_clone_and_install(projects_yaml, core_project, depth=1):
""" """
Clone/install all specified OpenStack repositories. Clone/install all specified OpenStack repositories.
@ -510,23 +524,22 @@ def git_clone_and_install(projects_yaml, core_project):
repository: 'git://git.openstack.org/openstack/requirements.git', repository: 'git://git.openstack.org/openstack/requirements.git',
branch: 'stable/icehouse'} branch: 'stable/icehouse'}
directory: /mnt/openstack-git directory: /mnt/openstack-git
http_proxy: http://squid.internal:3128 http_proxy: squid-proxy-url
https_proxy: https://squid.internal:3128 https_proxy: squid-proxy-url
The directory, http_proxy, and https_proxy keys are optional. The directory, http_proxy, and https_proxy keys are optional.
""" """
global requirements_dir global requirements_dir
parent_dir = '/mnt/openstack-git' parent_dir = '/mnt/openstack-git'
http_proxy = None
if not projects_yaml: projects = _git_yaml_load(projects_yaml)
return
projects = yaml.load(projects_yaml)
_git_validate_projects_yaml(projects, core_project) _git_validate_projects_yaml(projects, core_project)
old_environ = dict(os.environ) old_environ = dict(os.environ)
if 'http_proxy' in projects.keys(): if 'http_proxy' in projects.keys():
http_proxy = projects['http_proxy']
os.environ['http_proxy'] = projects['http_proxy'] os.environ['http_proxy'] = projects['http_proxy']
if 'https_proxy' in projects.keys(): if 'https_proxy' in projects.keys():
os.environ['https_proxy'] = projects['https_proxy'] os.environ['https_proxy'] = projects['https_proxy']
@ -534,15 +547,19 @@ def git_clone_and_install(projects_yaml, core_project):
if 'directory' in projects.keys(): if 'directory' in projects.keys():
parent_dir = projects['directory'] parent_dir = projects['directory']
pip_create_virtualenv(os.path.join(parent_dir, 'venv'))
for p in projects['repositories']: for p in projects['repositories']:
repo = p['repository'] repo = p['repository']
branch = p['branch'] branch = p['branch']
if p['name'] == 'requirements': if p['name'] == 'requirements':
repo_dir = _git_clone_and_install_single(repo, branch, parent_dir, repo_dir = _git_clone_and_install_single(repo, branch, depth,
parent_dir, http_proxy,
update_requirements=False) update_requirements=False)
requirements_dir = repo_dir requirements_dir = repo_dir
else: else:
repo_dir = _git_clone_and_install_single(repo, branch, parent_dir, repo_dir = _git_clone_and_install_single(repo, branch, depth,
parent_dir, http_proxy,
update_requirements=True) update_requirements=True)
os.environ = old_environ os.environ = old_environ
@ -574,7 +591,8 @@ def _git_ensure_key_exists(key, keys):
error_out('openstack-origin-git key \'{}\' is missing'.format(key)) error_out('openstack-origin-git key \'{}\' is missing'.format(key))
def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements): def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
update_requirements):
""" """
Clone and install a single git repository. Clone and install a single git repository.
""" """
@ -587,7 +605,8 @@ def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements)
if not os.path.exists(dest_dir): if not os.path.exists(dest_dir):
juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch)) juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
repo_dir = install_remote(repo, dest=parent_dir, branch=branch) repo_dir = install_remote(repo, dest=parent_dir, branch=branch,
depth=depth)
else: else:
repo_dir = dest_dir repo_dir = dest_dir
@ -598,7 +617,12 @@ def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements)
_git_update_requirements(repo_dir, requirements_dir) _git_update_requirements(repo_dir, requirements_dir)
juju_log('Installing git repo from dir: {}'.format(repo_dir)) juju_log('Installing git repo from dir: {}'.format(repo_dir))
pip_install(repo_dir) if http_proxy:
pip_install(repo_dir, proxy=http_proxy,
venv=os.path.join(parent_dir, 'venv'))
else:
pip_install(repo_dir,
venv=os.path.join(parent_dir, 'venv'))
return repo_dir return repo_dir
@ -621,16 +645,27 @@ def _git_update_requirements(package_dir, reqs_dir):
os.chdir(orig_dir) os.chdir(orig_dir)
def git_pip_venv_dir(projects_yaml):
"""
Return the pip virtualenv path.
"""
parent_dir = '/mnt/openstack-git'
projects = _git_yaml_load(projects_yaml)
if 'directory' in projects.keys():
parent_dir = projects['directory']
return os.path.join(parent_dir, 'venv')
def git_src_dir(projects_yaml, project): def git_src_dir(projects_yaml, project):
""" """
Return the directory where the specified project's source is located. Return the directory where the specified project's source is located.
""" """
parent_dir = '/mnt/openstack-git' parent_dir = '/mnt/openstack-git'
if not projects_yaml: projects = _git_yaml_load(projects_yaml)
return
projects = yaml.load(projects_yaml)
if 'directory' in projects.keys(): if 'directory' in projects.keys():
parent_dir = projects['directory'] parent_dir = projects['directory']
@ -640,3 +675,15 @@ def git_src_dir(projects_yaml, project):
return os.path.join(parent_dir, os.path.basename(p['repository'])) return os.path.join(parent_dir, os.path.basename(p['repository']))
return None return None
def git_yaml_value(projects_yaml, key):
"""
Return the value in projects_yaml for the specified key.
"""
projects = _git_yaml_load(projects_yaml)
if key in projects.keys():
return projects[key]
return None

View File

@ -17,8 +17,11 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import os
import subprocess
from charmhelpers.fetch import apt_install, apt_update from charmhelpers.fetch import apt_install, apt_update
from charmhelpers.core.hookenv import log from charmhelpers.core.hookenv import charm_dir, log
try: try:
from pip import main as pip_execute from pip import main as pip_execute
@ -51,11 +54,15 @@ def pip_install_requirements(requirements, **options):
pip_execute(command) pip_execute(command)
def pip_install(package, fatal=False, upgrade=False, **options): def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
"""Install a python package""" """Install a python package"""
command = ["install"] if venv:
venv_python = os.path.join(venv, 'bin/pip')
command = [venv_python, "install"]
else:
command = ["install"]
available_options = ('proxy', 'src', 'log', "index-url", ) available_options = ('proxy', 'src', 'log', 'index-url', )
for option in parse_options(options, available_options): for option in parse_options(options, available_options):
command.append(option) command.append(option)
@ -69,7 +76,10 @@ def pip_install(package, fatal=False, upgrade=False, **options):
log("Installing {} package with options: {}".format(package, log("Installing {} package with options: {}".format(package,
command)) command))
pip_execute(command) if venv:
subprocess.check_call(command)
else:
pip_execute(command)
def pip_uninstall(package, **options): def pip_uninstall(package, **options):
@ -94,3 +104,16 @@ def pip_list():
"""Returns the list of current python installed packages """Returns the list of current python installed packages
""" """
return pip_execute(["list"]) return pip_execute(["list"])
def pip_create_virtualenv(path=None):
"""Create an isolated Python environment."""
apt_install('python-virtualenv')
if path:
venv_path = path
else:
venv_path = os.path.join(charm_dir(), 'venv')
if not os.path.exists(venv_path):
subprocess.check_call(['virtualenv', venv_path])

View File

@ -45,14 +45,16 @@ class GitUrlFetchHandler(BaseFetchHandler):
else: else:
return True return True
def clone(self, source, dest, branch): def clone(self, source, dest, branch, depth=None):
if not self.can_handle(source): if not self.can_handle(source):
raise UnhandledSource("Cannot handle {}".format(source)) raise UnhandledSource("Cannot handle {}".format(source))
repo = Repo.clone_from(source, dest) if depth:
repo.git.checkout(branch) Repo.clone_from(source, dest, branch=branch, depth=depth)
else:
Repo.clone_from(source, dest, branch=branch)
def install(self, source, branch="master", dest=None): def install(self, source, branch="master", dest=None, depth=None):
url_parts = self.parse_url(source) url_parts = self.parse_url(source)
branch_name = url_parts.path.strip("/").split("/")[-1] branch_name = url_parts.path.strip("/").split("/")[-1]
if dest: if dest:
@ -63,7 +65,7 @@ class GitUrlFetchHandler(BaseFetchHandler):
if not os.path.exists(dest_dir): if not os.path.exists(dest_dir):
mkdir(dest_dir, perms=0o755) mkdir(dest_dir, perms=0o755)
try: try:
self.clone(source, dest_dir, branch) self.clone(source, dest_dir, branch, depth)
except GitCommandError as e: except GitCommandError as e:
raise UnhandledSource(e.message) raise UnhandledSource(e.message)
except OSError as e: except OSError as e:

View File

@ -9,6 +9,7 @@ from charmhelpers.contrib.openstack.utils import (
git_install_requested, git_install_requested,
git_clone_and_install, git_clone_and_install,
git_src_dir, git_src_dir,
git_pip_venv_dir,
) )
from collections import OrderedDict from collections import OrderedDict
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
@ -45,6 +46,7 @@ from charmhelpers.core.templating import render
BASE_GIT_PACKAGES = [ BASE_GIT_PACKAGES = [
'libxml2-dev', 'libxml2-dev',
'libxslt1-dev', 'libxslt1-dev',
'libyaml-dev',
'openvswitch-switch', 'openvswitch-switch',
'python-dev', 'python-dev',
'python-pip', 'python-pip',
@ -262,13 +264,27 @@ def git_post_install(projects_yaml):
shutil.rmtree(c['dest']) shutil.rmtree(c['dest'])
shutil.copytree(c['src'], c['dest']) shutil.copytree(c['src'], c['dest'])
# NOTE(coreycb): Need to find better solution than bin symlinks.
symlinks = [
{'src': os.path.join(git_pip_venv_dir(projects_yaml),
'bin/neutron-rootwrap'),
'link': '/usr/local/bin/neutron-rootwrap'},
]
for s in symlinks:
if os.path.lexists(s['link']):
os.remove(s['link'])
os.symlink(s['src'], s['link'])
render('git/neutron_sudoers', '/etc/sudoers.d/neutron_sudoers', {}, render('git/neutron_sudoers', '/etc/sudoers.d/neutron_sudoers', {},
perms=0o440) perms=0o440)
bin_dir = os.path.join(git_pip_venv_dir(projects_yaml), 'bin')
neutron_ovs_agent_context = { neutron_ovs_agent_context = {
'service_description': 'Neutron OpenvSwitch Plugin Agent', 'service_description': 'Neutron OpenvSwitch Plugin Agent',
'charm_name': 'neutron-openvswitch', 'charm_name': 'neutron-openvswitch',
'process_name': 'neutron-openvswitch-agent', 'process_name': 'neutron-openvswitch-agent',
'executable_name': os.path.join(bin_dir, 'neutron-openvswitch-agent'),
'cleanup_process_name': 'neutron-ovs-cleanup', 'cleanup_process_name': 'neutron-ovs-cleanup',
'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini', 'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
'log_file': '/var/log/neutron/openvswitch-agent.log', 'log_file': '/var/log/neutron/openvswitch-agent.log',
@ -278,6 +294,7 @@ def git_post_install(projects_yaml):
'service_description': 'Neutron OpenvSwitch Cleanup', 'service_description': 'Neutron OpenvSwitch Cleanup',
'charm_name': 'neutron-openvswitch', 'charm_name': 'neutron-openvswitch',
'process_name': 'neutron-ovs-cleanup', 'process_name': 'neutron-ovs-cleanup',
'executable_name': os.path.join(bin_dir, 'neutron-ovs-cleanup'),
'log_file': '/var/log/neutron/ovs-cleanup.log', 'log_file': '/var/log/neutron/ovs-cleanup.log',
} }

View File

@ -11,7 +11,7 @@ end script
pre-start script pre-start script
[ ! -x /usr/bin/{{ process_name }} ] && exit 0 [ ! -x /usr/bin/{{ process_name }} ] && exit 0
start-stop-daemon --start --chuid neutron --exec /usr/local/bin/{{ process_name }} -- \ start-stop-daemon --start --chuid neutron --exec {{ executable_name }} -- \
--log-file /var/log/neutron/{{ log_file }} \ --log-file /var/log/neutron/{{ log_file }} \
--config-file /etc/neutron/neutron.conf --verbose --config-file /etc/neutron/neutron.conf --verbose
end script end script

View File

@ -13,6 +13,6 @@ pre-start script
chown neutron:root /var/run/neutron chown neutron:root /var/run/neutron
end script end script
exec start-stop-daemon --start --chuid neutron --exec /usr/local/bin/{{ process_name }} -- \ exec start-stop-daemon --start --chuid neutron --exec {{ executable_name }} -- \
--config-file=/etc/neutron/neutron.conf --config-file={{ plugin_config }} \ --config-file=/etc/neutron/neutron.conf --config-file={{ plugin_config }} \
--log-file={{ log_file }} --log-file={{ log_file }}

View File

@ -72,10 +72,10 @@ class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
openstack_origin_git = { openstack_origin_git = {
'repositories': [ 'repositories': [
{'name': 'requirements', {'name': 'requirements',
'repository': 'git://git.openstack.org/openstack/requirements', 'repository': 'git://github.com/openstack/requirements',
'branch': branch}, 'branch': branch},
{'name': 'neutron', {'name': 'neutron',
'repository': 'git://git.openstack.org/openstack/neutron', 'repository': 'git://github.com/openstack/neutron',
'branch': branch}, 'branch': branch},
], ],
'directory': '/mnt/openstack-git', 'directory': '/mnt/openstack-git',

View File

@ -265,10 +265,11 @@ class TestNeutronOVSUtils(CharmTestCase):
@patch.object(nutils, 'render') @patch.object(nutils, 'render')
@patch('os.path.join') @patch('os.path.join')
@patch('os.path.exists') @patch('os.path.exists')
@patch('os.symlink')
@patch('shutil.copytree') @patch('shutil.copytree')
@patch('shutil.rmtree') @patch('shutil.rmtree')
def test_git_post_install(self, rmtree, copytree, exists, join, render, def test_git_post_install(self, rmtree, copytree, symlink, exists, join,
service_restart, git_src_dir): render, service_restart, git_src_dir):
projects_yaml = openstack_origin_git projects_yaml = openstack_origin_git
join.return_value = 'joined-string' join.return_value = 'joined-string'
nutils.git_post_install(projects_yaml) nutils.git_post_install(projects_yaml)
@ -278,10 +279,15 @@ class TestNeutronOVSUtils(CharmTestCase):
call('joined-string', '/etc/neutron/rootwrap.d'), call('joined-string', '/etc/neutron/rootwrap.d'),
] ]
copytree.assert_has_calls(expected) copytree.assert_has_calls(expected)
expected = [
call('joined-string', '/usr/local/bin/neutron-rootwrap'),
]
symlink.assert_has_calls(expected, any_order=True)
neutron_ovs_agent_context = { neutron_ovs_agent_context = {
'service_description': 'Neutron OpenvSwitch Plugin Agent', 'service_description': 'Neutron OpenvSwitch Plugin Agent',
'charm_name': 'neutron-openvswitch', 'charm_name': 'neutron-openvswitch',
'process_name': 'neutron-openvswitch-agent', 'process_name': 'neutron-openvswitch-agent',
'executable_name': 'joined-string',
'cleanup_process_name': 'neutron-ovs-cleanup', 'cleanup_process_name': 'neutron-ovs-cleanup',
'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini', 'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
'log_file': '/var/log/neutron/openvswitch-agent.log', 'log_file': '/var/log/neutron/openvswitch-agent.log',
@ -290,6 +296,7 @@ class TestNeutronOVSUtils(CharmTestCase):
'service_description': 'Neutron OpenvSwitch Cleanup', 'service_description': 'Neutron OpenvSwitch Cleanup',
'charm_name': 'neutron-openvswitch', 'charm_name': 'neutron-openvswitch',
'process_name': 'neutron-ovs-cleanup', 'process_name': 'neutron-ovs-cleanup',
'executable_name': 'joined-string',
'log_file': '/var/log/neutron/ovs-cleanup.log', 'log_file': '/var/log/neutron/ovs-cleanup.log',
} }
expected = [ expected = [