Support for Configuration Language. File Downloading

Execution plan files can be tranfered as binaries as just an URL to be download.
This code download the file when it is a URL

Partially-Implements Blueprint: conf-language-support

Change-Id: I102546a04a93dc74a4647cc8ad7a33d78ebca86b
This commit is contained in:
Henar Muñoz Frutos 2015-02-26 17:06:54 +01:00
parent fdc8de63fe
commit d7af39d3dd
8 changed files with 306 additions and 28 deletions

View File

@ -4,7 +4,7 @@
# 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
# 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,
@ -14,11 +14,16 @@
# limitations under the License.
import base64
import git
import os
import requests
import shutil
import subprocess
import urlparse
from muranoagent.common import config
CONF = config.CONF
@ -34,22 +39,33 @@ class FilesManager(object):
os.makedirs(self._cache_folder)
def put_file(self, file_id, script):
if type(file_id) is dict:
file_name = file_id.keys()[0]
file_def = file_id[file_name]
else:
file_name = file_id
file_def = self._files[file_name]
if file_def.get('Type') == 'Downloadable':
self._download_url_file(file_def)
return self._cache_folder
else:
return self._make_symlink(file_id, file_name, script)
def _make_symlink(self, file_id, file_name, script):
cache_path = self._fetch_file(file_id)
script_folder = os.path.join(self._cache_folder, script)
if not os.path.exists(script_folder):
if not os.path.isdir(script_folder):
os.mkdir(script_folder)
file_folder = os.path.join(script_folder,
os.path.dirname(file_name))
if not os.path.isdir(file_folder):
os.makedirs(file_folder)
filedef = self._files[file_id]
filename = filedef['Name']
file_folder = os.path.join(script_folder, os.path.dirname(filename))
if not os.path.exists(file_folder):
os.makedirs(file_folder)
script_path = os.path.join(script_folder, filename)
os.symlink(cache_path, script_path)
if cache_path is not None:
script_path = os.path.join(os.getcwd(), script_folder, file_name)
if not os.path.lexists(script_path):
os.symlink(cache_path, script_path)
return script_path
def _fetch_file(self, file_id):
@ -68,7 +84,59 @@ class FilesManager(object):
self._fetched_files[file_id] = out_path
return out_path
def _download_url_file(self, file_def):
"""It download the file in the murano-agent. It can proceed
from a git file or any other internal URL
"""
if 'URL' not in file_def:
raise ValueError("No valid URL in file {0}".
format(file_def))
url_file = file_def['URL']
if not self._url(url_file):
raise ValueError("Provided URL is not valid {0}".
format(url_file))
if not os.path.isdir(os.path.join(self._cache_folder, 'files')):
os.makedirs(os.path.join(self._cache_folder, 'files'))
folder = self._get_file_folder(url_file)
if self._is_git_repository(url_file):
if not os.path.isdir(folder):
git.Git().clone(url_file, folder)
else:
self._download_file(url_file, folder)
def clear(self):
os.chdir(os.path.dirname(self._cache_folder))
shutil.rmtree(self._cache_folder, ignore_errors=True)
shutil.rmtree(self._cache_folder, ignore_errors=True)
def _download_file(self, url, path):
local_filename = url.split('/')[-1]
r = requests.get(url, stream=True)
with open(os.path.join(path, local_filename), 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
f.flush()
return local_filename
def _url(self, file):
return (urlparse.urlsplit(file).scheme or
urlparse.urlsplit(file).netloc)
def _get_file_folder(self, file_url):
if file_url.endswith('.git'):
file_folder = file_url[file_url.rfind('/') + 1:
file_url.find('.git')]
else:
file_folder = file_url[file_url.rfind('/') + 1:]
folder = os.path.join(self._cache_folder, 'files', file_folder)
return folder
def _is_git_repository(self, url):
return url.startswith(("git://",
"git+http://", "git+https:/"))

View File

@ -1,13 +0,0 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# 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.

View File

View File

@ -0,0 +1,29 @@
# 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 fixtures
from oslo.config import cfg
import testtools
CONF = cfg.CONF
class MuranoAgentTestCase(testtools.TestCase):
def setUp(self):
super(MuranoAgentTestCase, self).setUp()
self.useFixture(fixtures.FakeLogger('murano-agent'))
def override_config(self, name, override, group=None):
CONF.set_override(name, override, group)
self.addCleanup(CONF.clear_override, name, group)

View File

@ -0,0 +1,192 @@
# Copyright (c) 2015 Telefonica I+D
#
# 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 bunch
import git
import mock
from muranoagent.common import config as cfg
from muranoagent import files_manager
from muranoagent.tests.unit import base
CONF = cfg.CONF
class TestFileManager(base.MuranoAgentTestCase):
@mock.patch('os.path.isdir')
@mock.patch('os.mkdir')
@mock.patch('os.makedirs')
def setUp(self, mock_makedir, mock_mkdir, mock_path):
mock_path.return_value = True
mock_mkdir.return_value = None
mock_makedir.return_value = None
super(TestFileManager, self).setUp()
CONF.set_override('storage', 'cache')
@mock.patch('os.makedirs')
def test_get_folder_git(self, mock_path):
"""It gets the folder where the URL is a git URL."""
mock_path.return_value = None
files = files_manager.FilesManager(self.get_template_downloable())
folder = files._get_file_folder("http://tomcat.git")
self.assertEqual(folder, "cache/files/ID/files/tomcat")
@mock.patch('os.makedirs')
def test_get_folder_not_git(self, mock_path):
"""It gets the folder from the URL."""
mock_path.return_value = None
files = files_manager.FilesManager(self.get_template_downloable())
folder = files._get_file_folder("http://tomcat")
self.assertEqual(folder, "cache/files/ID/files/tomcat")
@mock.patch("git.Git")
@mock.patch('os.path.isdir')
@mock.patch('os.makedirs')
def test_execution_plan_type_downloable_git(self, mock_makedir, mock_path,
mock_git):
"""It tests an execution plan when there are files
which should be downloable.
"""
mock_makedir.return_value = None
mock_path.return_value = True
mock_git.clone.return_value = None
template = self.get_template_downloable_git()
files = files_manager.FilesManager(self.get_template_downloable())
files._download_url_file(template.Files['mycoockbook'])
@mock.patch('os.path.isdir')
@mock.patch('os.mkdir')
@mock.patch('os.makedirs')
@mock.patch('__builtin__.open')
@mock.patch('requests.get')
def test_execution_plan_type_downloable(self, mock_requests, open_mock,
mock_makedir,
mock_mkdir, mock_path):
"""It tests an execution plan when there are files
which should be downloable.
"""
mock_path.return_value = True
mock_mkdir.return_value = None
mock_makedir.return_value = None
mock_requests.return_value = None
self._open_mock(open_mock)
template = self.get_template_downloable()
files = files_manager.FilesManager(self.get_template_downloable())
files._download_url_file(template.Files['file'])
@mock.patch('os.makedirs')
def test_execution_plan_type_downloable_no_Url(self, mock_makedir):
"""It validates the URL."""
mock_makedir.return_value = None
template = bunch.Bunch(
ID='ID',
Files={
'mycoockbook': {
'Name': 'mycoockbook.txt',
'Type': 'Downloadable'
}
}
)
files = files_manager.FilesManager(template)
self.assertRaises(ValueError, files._download_url_file,
template.Files['mycoockbook'])
@mock.patch("git.Git")
@mock.patch('os.path.isdir')
@mock.patch('os.makedirs')
def test_putfile_downloable(self, mock_makedir, mock_git, path):
"""It tests the putfile method when the file is a git
URL.
"""
path.return_value = True
mock_git.clone.return_value = None
mock_makedir.return_value = None
template = self.get_template_downloable_git()
files = files_manager.FilesManager(template)
for file in template.get('Files'):
files.put_file(file, 'deploy')
@mock.patch('__builtin__.open')
@mock.patch('os.path.lexists')
@mock.patch('os.path.isdir')
@mock.patch('os.makedirs')
def test_putfile_file(self, mock_makedir, mock_path,
mock_exists, open_mock):
"""It tests the putfile method."""
mock_path.return_value = True
mock_makedir.return_value = None
mock_exists.return_value = True
context_manager_mock = mock.Mock()
open_mock.return_value = context_manager_mock
file_mock = mock.Mock()
file_mock.read.return_value = ''
enter_mock = mock.Mock()
enter_mock.return_value = file_mock
exit_mock = mock.Mock()
setattr(context_manager_mock, '__enter__', enter_mock)
setattr(context_manager_mock, '__exit__', exit_mock)
template = self.get_template_file()
files = files_manager.FilesManager(template)
for file in template.get('Files'):
files.put_file(file, 'deploy')
def get_template_downloable_git(self):
return bunch.Bunch(
ID='ID',
Files={
'mycoockbook': {
'Name': 'mycoockbook.txt',
'URL': 'git://github.com/tomcat.git',
'Type': 'Downloadable'
}
}
)
def get_template_downloable(self):
return bunch.Bunch(
ID='ID',
Files={
'file': {
'Name': 'myfile',
'URL': 'https://www.apache.org/licenses/LICENSE-2.0',
'Type': 'Downloadable'
}
}
)
def get_template_file(self):
return bunch.Bunch(
ID='ID',
Files={
'test': {
'Body': 'dGV4dA==\n',
'BodyType': 'Base64',
'Name': 'installer'
}
}
)
def _open_mock(self, open_mock):
context_manager_mock = mock.Mock()
open_mock.return_value = context_manager_mock
file_mock = mock.Mock()
file_mock.read.return_value = ''
enter_mock = mock.Mock()
enter_mock.return_value = file_mock
exit_mock = mock.Mock()
setattr(context_manager_mock, '__enter__', enter_mock)
setattr(context_manager_mock, '__exit__', exit_mock)

View File

@ -14,3 +14,4 @@ stevedore>=1.0.0 # Apache-2.0
# not listed in global requirements
semver!=2.0
bunch
requests

View File

@ -1,8 +1,9 @@
hacking>=0.8.0,<0.9
unittest2
coverage>=3.6
discover
mock>=1.0
gitpython
sphinx>=1.1.2
testtools>=0.9.32
testrepository>=0.0.18