Alon Bar-Lev has uploaded a new change for review. Change subject: host-deploy: use pyar instead of tar ......................................................................
host-deploy: use pyar instead of tar pyar = python archive, self extracting python script. for some reason fedora-18/19 got tar out of base system, so we are forced to find some solution, although manual configuration is required anyway (repository, networking etc...). there was attempt to use python tar module, however this module has a bug when last block is empty, so it is not suitable for usage. Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=986882 Change-Id: I1e5ddab9ae87979da7d7296c4f3c7ef08e21e903 Signed-off-by: Alon Bar-Lev <[email protected]> --- M backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsDeploy.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java M backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/EngineLocalConfig.java R backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/CachedPyAr.java A backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/PyAr.java D backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/Tar.java D backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/archivers/tar/TarTest.java M ovirt-engine.spec.in A packaging/conf/pyar.py M packaging/services/ovirt-engine/ovirt-engine.conf.in 10 files changed, 222 insertions(+), 349 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/95/17295/1 diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsDeploy.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsDeploy.java index 36d3684..f62ee9e 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsDeploy.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsDeploy.java @@ -35,7 +35,7 @@ import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.EngineLocalConfig; import org.ovirt.engine.core.utils.NetworkUtils; -import org.ovirt.engine.core.utils.archivers.tar.CachedTar; +import org.ovirt.engine.core.utils.archivers.CachedPyAr; import org.ovirt.engine.core.utils.crypt.EngineEncryptionUtils; import org.ovirt.engine.core.utils.hostinstall.OpenSslCAWrapper; import org.ovirt.engine.core.utils.linq.LinqUtils; @@ -81,7 +81,7 @@ private static final String BOOTSTRAP_CUSTOM_ENVIRONMENT_PLACE_HOLDER = "@ENVIRONMENT@"; private static final Log log = LogFactory.getLog(VdsDeploy.class); - private static volatile CachedTar s_deployPackage; + private static volatile CachedPyAr s_deployPackage; private SSHDialog.Control _control; private Thread _thread; @@ -886,7 +886,7 @@ ); if (s_deployPackage == null) { - s_deployPackage = new CachedTar( + s_deployPackage = new CachedPyAr( new File( EngineLocalConfig.getInstance().getCacheDir(), Config.<String> GetValue(ConfigValues.BootstrapPackageName) diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java index e3298f9..d927939 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java @@ -1065,12 +1065,10 @@ @TypeConverterAttribute(String.class) @DefaultValueAttribute( "umask 0077; " + - "MYTMP=\"$(mktemp -t ovirt-XXXXXXXXXX)\"; " + + "MYTMP=\"$(mktemp -d -t ovirt-XXXXXXXXXX)\"; " + "trap \"chmod -R u+rwX \\\"${MYTMP}\\\" > /dev/null 2>&1; rm -fr \\\"${MYTMP}\\\" > /dev/null 2>&1\" 0; " + - "rm -fr \"${MYTMP}\" && " + - "mkdir \"${MYTMP}\" && " + - "tar --warning=no-timestamp -C \"${MYTMP}\" -x && " + - "@ENVIRONMENT@ \"${MYTMP}\"/setup DIALOG/dialect=str:machine DIALOG/customization=bool:True" + "IFS=''; while read l; do [ \"${l}\" == \"#__EOF__\" ] && break || echo \"${l}\" >> \"${MYTMP}/s\"; done; " + + "@ENVIRONMENT@ python \"${MYTMP}/s\" DIALOG/dialect=str:machine DIALOG/customization=bool:True" ) BootstrapCommand(373), @@ -1081,7 +1079,7 @@ @DefaultValueAttribute("/usr/share/ovirt-host-deploy/interface-3") BootstrapPackageDirectory(375), @TypeConverterAttribute(String.class) - @DefaultValueAttribute("ovirt-host-deploy.tar") + @DefaultValueAttribute("ovirt-host-deploy.py") BootstrapPackageName(376), @Reloadable @TypeConverterAttribute(String.class) diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/EngineLocalConfig.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/EngineLocalConfig.java index d03521b..1bf7bf7 100644 --- a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/EngineLocalConfig.java +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/EngineLocalConfig.java @@ -209,4 +209,8 @@ public String getPKIEngineStoreAlias() { return getProperty("ENGINE_PKI_ENGINE_STORE_ALIAS"); } + + public File getPyArFile() { + return getFile("ENGINE_PYAR_FILE"); + } } diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/CachedTar.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/CachedPyAr.java similarity index 62% rename from backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/CachedTar.java rename to backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/CachedPyAr.java index d4c6bdf..c919ea1 100644 --- a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/CachedTar.java +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/CachedPyAr.java @@ -1,4 +1,4 @@ -package org.ovirt.engine.core.utils.archivers.tar; +package org.ovirt.engine.core.utils.archivers; import java.io.File; import java.io.FileOutputStream; @@ -16,50 +16,39 @@ * Cache tar based on directory structure. If files are changed * recreate tar file. Test file change once per interval. */ -public class CachedTar { +public class CachedPyAr { - private static final Log log = LogFactory.getLog(CachedTar.class); + private static final Log log = LogFactory.getLog(CachedPyAr.class); - private long refreshInterval = 10000; - private long nextCheckTime = 0; + private long _refreshInterval = 10000; + private long _nextCheckTime = 0; - private File archive; - private File dir; + private File _archive; + private File _dir; private void create(long timestamp) throws IOException { File temp = null; - OutputStream os = null; try { // must create within same directory // so rename be atomic temp = File.createTempFile( - this.archive.getName(), + _archive.getName(), "tmp", - this.archive.getParentFile() + _archive.getParentFile() ); - os = new FileOutputStream(temp); - Tar.doTar(os, this.dir); - } - catch(IOException e) { - String message = String.format("Cannot create tarball '%1$s'", this.archive); - log.error(message, e); - throw new IOException(message, e); - } - finally { - try { - if (os != null) { - os.close(); - } + try (OutputStream out = new FileOutputStream(temp)) { + PyAr.doArchive(out, _dir, "setup"); } catch(IOException e) { - log.error( - String.format("Cannot close '%1$s'", temp), + throw new IOException( + String.format( + "Cannot create pyar '%1$s'", + _archive + ), e ); } - } - try { if (!temp.setLastModified(timestamp)) { throw new IOException( String.format( @@ -70,12 +59,12 @@ ); } - if (!temp.renameTo(this.archive)) { + if (!temp.renameTo(_archive)) { throw new IOException( String.format( "Cannot rename '%1$s' to '%2$s'", temp.getCanonicalPath(), - archive.getCanonicalPath() + _archive.getCanonicalPath() ) ); } @@ -94,25 +83,25 @@ } private void ensure() throws IOException { - if (!this.archive.exists()) { + if (!_archive.exists()) { log.info( String.format( - "Tarball '%1$s' is missing, creating", - this.archive.getAbsolutePath() + "PyAr '%1$s' is missing, creating", + _archive.getAbsolutePath() ) ); - this.nextCheckTime = System.currentTimeMillis() + this.refreshInterval; - create(getTimestampRecursive(this.dir)); + _nextCheckTime = System.currentTimeMillis() + _refreshInterval; + create(getTimestampRecursive(_dir)); } - else if (this.nextCheckTime <= System.currentTimeMillis()) { - this.nextCheckTime = System.currentTimeMillis() + this.refreshInterval; + else if (_nextCheckTime <= System.currentTimeMillis()) { + _nextCheckTime = System.currentTimeMillis() + _refreshInterval; - long treeTimestamp = getTimestampRecursive(this.dir); - if (archive.lastModified() != treeTimestamp) { + long treeTimestamp = getTimestampRecursive(_dir); + if (_archive.lastModified() != treeTimestamp) { log.info( String.format( - "Tarball '%1$s' is out of date, re-creating", - this.archive.getAbsolutePath() + "PyAr '%1$s' is out of date, re-creating", + _archive.getAbsolutePath() ) ); create(treeTimestamp); @@ -125,11 +114,11 @@ * @param archive name of tar to cache. * @param dir base directory. */ - public CachedTar(File archive, File dir) { - this.archive = archive; - this.dir = dir; + public CachedPyAr(File archive, File dir) { + _archive = archive; + _dir = dir; - this.refreshInterval = Config.<Integer>GetValue( + _refreshInterval = Config.<Integer>GetValue( ConfigValues.BootstrapCacheRefreshInterval ); } @@ -140,7 +129,7 @@ * @return File name. */ public File getFileNoUse() { - return this.archive; + return _archive; } /** @@ -149,7 +138,7 @@ */ public File getFile() throws IOException { ensure(); - return this.archive; + return _archive; } /** diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/PyAr.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/PyAr.java new file mode 100644 index 0000000..303e108 --- /dev/null +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/PyAr.java @@ -0,0 +1,118 @@ +package org.ovirt.engine.core.utils.archivers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.codec.binary.Base64OutputStream; + +import org.ovirt.engine.core.utils.EngineLocalConfig; + +public class PyAr { + + private static final Log log = LogFactory.getLog(PyAr.class); + + private static void _recurse( + OutputStream out, + File file, + String base + ) throws SecurityException, IOException { + + if (file.isDirectory()) { + for (String f : file.list()) { + _recurse( + out, + new File(file, f), + new File(base, f).getPath() + ); + } + } + else if (file.isFile()) { + int mode; + if (file.canExecute()) { + mode = 0700; + } + else { + mode = 0600; + } + out.write( + String.format( + "{'name':'%s','mode':0o%03o,'data':'''\n", + base, + mode + ).getBytes(Charset.forName("UTF-8")) + ); + try( + InputStream in = new FileInputStream(file); + OutputStream b64out = new Base64OutputStream( + new FilterOutputStream(out) { + public void close() throws IOException { + flush(); + // no close + } + }, + true, + 76, + new byte[] {'\n'} + ) + ) { + byte[] buf = new byte[1024]; + int n; + while ((n = in.read(buf)) != -1) { + b64out.write(buf, 0, n); + } + } + out.write("'''},\n".getBytes(Charset.forName("UTF-8"))); + } + } + + /** + * Crete pyar. + * @param out output stream to write into. + * @param base base directory. + * + * Only regular files and directories are supported. + * Files will be owner rw and optional execute bit. + */ + public static void doArchive( + OutputStream out, + File base, + String entry + ) throws SecurityException, IOException { + + if (!base.exists()) { + throw new FileNotFoundException( + String.format( + "File or directory %1$s not found", + base + ) + ); + } + + try(InputStream in = new FileInputStream( + EngineLocalConfig.getInstance().getPyArFile() + )) { + byte[] buf = new byte[1024]; + int n; + while ((n = in.read(buf)) != -1) { + out.write(buf, 0, n); + } + } + out.write( + String.format( + "doit(entry='%s', files=[\n", + entry + ).getBytes(Charset.forName("UTF-8")) + ); + _recurse(out, base, "./"); + out.write("])\n".getBytes(Charset.forName("UTF-8"))); + out.write("#__EOF__\n".getBytes(Charset.forName("UTF-8"))); + } +} diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/Tar.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/Tar.java deleted file mode 100644 index c427f62..0000000 --- a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/archivers/tar/Tar.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.ovirt.engine.core.utils.archivers.tar; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; - -/** - * A simple recursive tar based on javatar. - */ -public class Tar { - - private static final Log log = LogFactory.getLog(Tar.class); - - private static void _recurse( - TarArchiveOutputStream archive, - File file, - String base - ) throws SecurityException, IOException { - - TarArchiveEntry entry = (TarArchiveEntry)archive.createArchiveEntry( - file, - base - ); - if (entry.getFile().isDirectory()) { - entry.setMode(0700); - archive.putArchiveEntry(entry); - archive.closeArchiveEntry(); - for (String f : file.list()) { - _recurse( - archive, - new File(entry.getFile(), f), - new File(entry.getName(), f).getPath() - ); - } - } - else if (entry.getFile().isFile()) { - if (entry.getFile().canExecute()) { - entry.setMode(0700); - } - else { - entry.setMode(0600); - } - archive.putArchiveEntry(entry); - InputStream is = null; - try { - is = new FileInputStream(entry.getFile()); - byte buffer[] = new byte[8192]; - int n; - while ((n = is.read(buffer)) != -1) { - archive.write(buffer, 0, n); - } - } - finally { - if (is != null) { - try { - is.close(); - } - catch(IOException e) { - log.error( - String.format( - "Cannot close file '%1$s'", - entry.getFile().getAbsolutePath() - ), - e - ); - } - } - } - archive.closeArchiveEntry(); - } - } - - /** - * Crete tar. - * @param os output stream to write into. - * @param base base directory. - * - * Only regular files and directories are supported. - * Files will be owner rw and optional execute bit. - */ - public static void doTar( - OutputStream os, - File base - ) throws SecurityException, IOException { - - if (!base.exists()) { - throw new FileNotFoundException( - String.format( - "File or directory %1$s not found", - base - ) - ); - } - - TarArchiveOutputStream archive = null; - try { - archive = new TarArchiveOutputStream(os); - // TODO: use LONGFILE_POSIX in newer version of commons-compress - archive.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); - _recurse(archive, base, "./"); - } - finally { - if (archive != null) { - try { - archive.close(); - } - catch(IOException e) { - log.error( - String.format("Cannot close tar stream"), - e - ); - } - } - } - } - - public static void main(String args[]) throws Exception { - FileOutputStream os = null; - try { - os = new FileOutputStream(args[0]); - Tar.doTar(os, new File(args[1])); - } - finally { - if (os != null) { - try { - os.close(); - } - catch(IOException e) {} - } - } - } -} diff --git a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/archivers/tar/TarTest.java b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/archivers/tar/TarTest.java deleted file mode 100644 index da965c1..0000000 --- a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/archivers/tar/TarTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.ovirt.engine.core.utils.archivers.tar; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.util.Arrays; - -import org.apache.commons.lang.SystemUtils; -import org.junit.Test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; - -public class TarTest { - - private void _writeFile(File file, String content, boolean executable) throws IOException { - OutputStream os = null; - try { - file.getParentFile().mkdirs(); - os = new FileOutputStream(file); - os.write(content.getBytes("UTF-8")); - file.setExecutable(executable); - } - finally { - if (os != null) { - try { - os.close(); - } - catch (IOException e) { - // ignore - } - } - } - } - - private void digestDirectory(MessageDigest md, File base, File file) throws Exception { - File fullFile = new File(base, file.getPath()); - md.update(file.getPath().getBytes("UTF-8")); - if (fullFile.isDirectory()) { - String files[] = fullFile.list(); - Arrays.sort(files); - for (String f : files) { - digestDirectory(md, base, new File(file, f)); - } - } - else { - MessageDigest fmd = MessageDigest.getInstance(md.getAlgorithm()); - InputStream fis = null; - InputStream is = null; - try { - fis = new FileInputStream(fullFile); - is = new DigestInputStream(fis, fmd); - byte buf[] = new byte[1024]; - int n; - while ((n = is.read(buf)) != -1) { - // do nothing - } - md.update(fmd.digest()); - } - finally { - if (fis != null) { - try { - fis.close(); - } - catch (IOException e) { - // ignore - } - } - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // ignore - } - } - } - } - } - - @Test - public void testSimple() throws Exception { - assumeTrue(SystemUtils.IS_OS_UNIX); - - File tmpTar = null; - File tmpDir1 = null; - File tmpDir2 = null; - - try { - tmpTar = File.createTempFile("test1", "tar"); - tmpDir1 = File.createTempFile("test1", "tmp"); - tmpDir1.delete(); - tmpDir1.mkdir(); - tmpDir2 = File.createTempFile("test1", "tmp"); - tmpDir2.delete(); - tmpDir2.mkdir(); - - _writeFile(new File(tmpDir1, "script1"), "script1", true); - _writeFile(new File(tmpDir1, "script2"), "script2", true); - _writeFile(new File(tmpDir1, "file1"), "file1", false); - _writeFile(new File(tmpDir1, "file2"), "file2", false); - _writeFile(new File(tmpDir1, "dir1/file3"), "file2", false); - _writeFile(new File(tmpDir1, "dir1/dir2/file4"), "file4", false); - - OutputStream os = null; - try { - os = new FileOutputStream(tmpTar); - Tar.doTar(os, tmpDir1); - } - finally { - if (os != null) { - try { - os.close(); - } - catch(IOException e) { - // ignore - } - } - } - - new ProcessBuilder("tar", "-C", tmpDir2.getPath(), "-xf", tmpTar.getPath()).start().waitFor(); - - MessageDigest md1 = MessageDigest.getInstance("SHA"); - MessageDigest md2 = MessageDigest.getInstance("SHA"); - digestDirectory(md1, tmpDir1, new File(".")); - digestDirectory(md2, tmpDir2, new File(".")); - - assertArrayEquals(md1.digest(), md2.digest()); - assertTrue(new File(tmpDir2, "script1").canExecute()); - assertFalse(new File(tmpDir2, "file1").canExecute()); - } - finally { - for (File file : new File[] {tmpDir1, tmpDir2, tmpTar}) { - if (file != null) { - file.delete(); - } - } - } - } - - @Test(expected=FileNotFoundException.class) - public void testNoBase() throws IOException { - Tar.doTar(new ByteArrayOutputStream(), new File("/asdasdsadasdasdsa")); - } -} diff --git a/ovirt-engine.spec.in b/ovirt-engine.spec.in index 9ee3a9e..1179383 100644 --- a/ovirt-engine.spec.in +++ b/ovirt-engine.spec.in @@ -742,6 +742,7 @@ %config(noreplace) %{engine_etc}/sysprep # Configuration files +%{engine_data}/conf/pyar.py %{engine_data}/conf/osinfo-defaults.properties %dir %{engine_etc}/osinfo.conf.d %{engine_etc}/osinfo.conf.d/00-defaults.properties diff --git a/packaging/conf/pyar.py b/packaging/conf/pyar.py new file mode 100755 index 0000000..138da81 --- /dev/null +++ b/packaging/conf/pyar.py @@ -0,0 +1,52 @@ +#!/usr/bin/python + + +import sys +import os +import base64 +import tempfile +import shutil +import subprocess + + +def _safe(f): + if os.path.isabs(f): + raise RuntimeError('Absolute files are not permitted') + if os.path.normpath(f).startswith('..'): + raise RuntimeError('Files outside of root are not permitted') + + +def doit(entry=None, files=[]): + ret = 2 + root = None + try: + root = tempfile.mkdtemp(prefix='ovirt-') + if entry is not None: + _safe(entry) + entry = os.path.join(root, entry) + for f in files: + _safe(f['name']) + rname = os.path.join(root, f['name']) + if f.get('entry', False): + entry = rname + dir = os.path.dirname(rname) + if dir: + if not os.path.exists(dir): + os.makedirs(dir) + with open(rname, 'wb') as x: + x.write(base64.b64decode(f['data'].encode('utf8'))) + os.chmod(rname, f.get('mode', 0o600)) + if entry is None: + raise RuntimeError('No entry') + p = subprocess.Popen( + executable=entry, + args=[entry] + list(sys.argv[1:]), + ) + ret = p.wait() + except Exception as e: + sys.stderr.write('%s\n' % e) + ret = 2 + finally: + if root is not None: + shutil.rmtree(path=root, ignore_errors=True) + sys.exit(ret) diff --git a/packaging/services/ovirt-engine/ovirt-engine.conf.in b/packaging/services/ovirt-engine/ovirt-engine.conf.in index 4b1496d..e0008af 100644 --- a/packaging/services/ovirt-engine/ovirt-engine.conf.in +++ b/packaging/services/ovirt-engine/ovirt-engine.conf.in @@ -187,3 +187,8 @@ ENGINE_PKI_ENGINE_STORE=${ENGINE_PKI}/keys/engine.p12 ENGINE_PKI_ENGINE_STORE_PASSWORD= ENGINE_PKI_ENGINE_STORE_ALIAS=1 + +# +# PyAr body +# +ENGINE_PYAR_FILE="${ENGINE_USR}/conf/pyar.py" -- To view, visit http://gerrit.ovirt.org/17295 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I1e5ddab9ae87979da7d7296c4f3c7ef08e21e903 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Alon Bar-Lev <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
