env = pb.environment();
+ env.put("TPARAMS", params.get("tun"));
+ //System.out.println("TPARAMS: " + params.get("tun"));
+ //System.out.println("java: " + java + " -jar " + jarFileName + " " + MSTSC_CMD + " " + rdpFileName);
+ pb.start();
+
+ }
+
+ private void executeDirect() throws IOException
+ {
+ Runtime.getRuntime().exec( MSTSC_CMD + " \"" + rdpFileName + "\"" );
+ }
+
+ private boolean downloadDll()
+ {
+ return util.download(baseUrl, "2", dllFileName);
+ }
+
+ private boolean downloadJar()
+ {
+ return util.download(baseUrl, "3", jarFileName);
+ }
+
+}
diff --git a/trunk/rdptransport/java/src/es/virtualcable/rdp/util.java b/trunk/rdptransport/java/src/es/virtualcable/rdp/util.java
new file mode 100644
index 00000000..49502e5e
--- /dev/null
+++ b/trunk/rdptransport/java/src/es/virtualcable/rdp/util.java
@@ -0,0 +1,98 @@
+package es.virtualcable.rdp;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLEncoder;
+
+public class util {
+
+ public static boolean download(String baseUrl, String id, String outputFileName)
+ {
+ try {
+ java.net.URL u = new java.net.URL(baseUrl + id);
+ java.net.URLConnection uc = u.openConnection();
+ String contentType = uc.getContentType();
+ int contentLength = uc.getContentLength();
+ if (contentType.startsWith("text/") || contentLength == -1) {
+ throw new IOException("This is not a binary file.");
+ }
+ InputStream raw = uc.getInputStream();
+ InputStream in = new BufferedInputStream(raw);
+ byte[] data = new byte[contentLength];
+ int bytesRead = 0;
+ int offset = 0;
+ while (offset < contentLength) {
+ bytesRead = in.read(data, offset, data.length - offset);
+ if (bytesRead == -1)
+ break;
+ offset += bytesRead;
+ }
+ in.close();
+
+ if (offset != contentLength) {
+ throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
+ }
+
+ java.io.FileOutputStream out = new java.io.FileOutputStream(outputFileName);
+ out.write(data);
+ out.flush();
+ out.close();
+
+ } catch(Exception e) {
+ System.out.println("Unable to download file, already present or network error? " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+
+ public static String getUrl(String url) {
+ try {
+ java.net.URL u = new java.net.URL(url);
+ BufferedReader in = new BufferedReader(new InputStreamReader(u.openStream()));
+ StringBuilder data = new StringBuilder();
+
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ data.append(inputLine);
+ data.append("\n");
+ }
+
+ in.close();
+ return data.toString();
+
+ } catch(Exception e) {
+ System.out.println("Unable to get url. Network error? " + e.getMessage());
+ return null;
+ }
+
+ }
+
+ @SuppressWarnings("deprecation")
+ public static void notifyHostname(String baseUrl, String serviceId) {
+ String[] urlComponents = baseUrl.split("/");
+ String hostname;
+ String ip;
+ String url="";
+
+ try {
+ hostname = java.net.InetAddress.getLocalHost().getHostName();
+ ip = java.net.InetAddress.getLocalHost().getHostAddress();
+ } catch(Exception e) {
+ hostname = "unknown";
+ ip = "0.0.0.0";
+ }
+
+ try {
+ // An url is "http[s]://.....:/,
+ url = urlComponents[0] + "//" + urlComponents[2] + "/sernotify/" + serviceId + "/hostname?hostname="+URLEncoder.encode(hostname)+"&ip="+URLEncoder.encode(ip);
+ getUrl(url);
+ } catch(Exception e) {
+ System.out.println("Unable to get url? " + e.getMessage());
+ }
+ }
+
+}
diff --git a/trunk/rdptransport/java/src/manifest b/trunk/rdptransport/java/src/manifest
new file mode 100644
index 00000000..e3467bb0
--- /dev/null
+++ b/trunk/rdptransport/java/src/manifest
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Sealed: true
+
diff --git a/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPI.java b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPI.java
new file mode 100644
index 00000000..95f651ed
--- /dev/null
+++ b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPI.java
@@ -0,0 +1,27 @@
+package net.sourceforge.jdpapi;
+
+
+class DPAPI {
+
+ /**
+ * See the CryptProtectData
+ * documentation for more details
+ *
+ * @param input Plaintext to encrypt
+ * @param entropy Additional entropy to include when encrypting (optional)
+ * @param localMachine If true, allow all users on the machine to decrypt the return ciphertext
+ * @return ciphertext
+ */
+ static native byte [] CryptProtectData(String input, byte [] entropy, boolean localMachine);
+
+ /**
+ * See the CryptUnprotectData
+ * documentation for more details
+ *
+ * @param input Ciphertext
+ * @param entropy Entropy that was included when {@code input} was encrypted
+ * @return
+ */
+ static native String CryptUnprotectData(byte [] input, byte [] entropy);
+
+}
\ No newline at end of file
diff --git a/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPIException.java b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPIException.java
new file mode 100644
index 00000000..6bc5019a
--- /dev/null
+++ b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DPAPIException.java
@@ -0,0 +1,28 @@
+package net.sourceforge.jdpapi;
+
+/**
+ * Exception that can be thrown from the native DPAPI
+ *
+ * @author Kevin Conaway
+ */
+public class DPAPIException extends RuntimeException {
+
+ private static final long serialVersionUID = 1l;
+
+ public DPAPIException() {
+ super();
+ }
+
+ public DPAPIException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DPAPIException(String message) {
+ super(message);
+ }
+
+ public DPAPIException(Throwable cause) {
+ super(cause);
+ }
+
+}
\ No newline at end of file
diff --git a/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DataProtector.java b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DataProtector.java
new file mode 100644
index 00000000..7aed1808
--- /dev/null
+++ b/trunk/rdptransport/java/src/net/sourceforge/jdpapi/DataProtector.java
@@ -0,0 +1,75 @@
+package net.sourceforge.jdpapi;
+
+/**
+ * An interface to the Microsoft Data Protection API (DPAPI).
+ *
+ * See MSDN for more information
+ *
+ * @author Kevin Conaway
+ */
+public class DataProtector {
+
+ private final byte [] entropy;
+ private final boolean localMachine;
+
+ /**
+ * @param entropy Additional entropy to include when encrypting (optional)
+ * @param localMachine If true, allow all users on the machine to decrypt a given ciphertext
+ *
+ */
+ public DataProtector(byte[] entropy, boolean localMachine) {
+ this.entropy = entropy;
+ this.localMachine = localMachine;
+ }
+
+ /**
+ * @param entropy
+ * @see #DataProtector(byte[], boolean)
+ */
+ public DataProtector(byte[] entropy) {
+ this(entropy, false);
+ }
+
+ /**
+ * @param localMachine
+ * @see #DataProtector(byte[], boolean)
+ */
+ public DataProtector(boolean localMachine) {
+ this(null, localMachine);
+ }
+
+ /**
+ * Initializes the protector with no additional entropy
+ * and {@code localMachine} set to false
+ * @see #DataProtector(byte[], boolean)
+ */
+ public DataProtector() {
+ this(false);
+ }
+
+ /**
+ * Protect {@code input} using the Microsoft DPAPI CryptProtectData function.
+ *
+ * See the CryptProtectData
+ * documentation for more details
+ *
+ * @param input Plaintext to encrypt
+ * @return ciphertext
+ */
+ public byte[] protect(String input) {
+ return DPAPI.CryptProtectData(input, entropy, localMachine);
+ }
+
+ /**
+ * Unprotect {@code input} using the Microsoft DPAPI CryptUnprotectData function.
+ *
+ * See the CryptUnprotectData
+ * documentation for more details
+ *
+ * @param input Ciphertext
+ * @return Plaintext
+ */
+ public String unprotect(byte [] input) {
+ return DPAPI.CryptUnprotectData(input, entropy);
+ }
+}
\ No newline at end of file
diff --git a/trunk/rdptransport/rdppass.sln b/trunk/rdptransport/rdppass.sln
new file mode 100644
index 00000000..a2057bce
--- /dev/null
+++ b/trunk/rdptransport/rdppass.sln
@@ -0,0 +1,26 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rdppass", "rdppass\rdppass.vcxproj", "{93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testPass", "testPass\testPass.vcxproj", "{7616C72B-2571-4DF9-8BF7-8B32359CE489}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}.Debug|Win32.ActiveCfg = Debug|Win32
+ {93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}.Debug|Win32.Build.0 = Debug|Win32
+ {93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}.Release|Win32.ActiveCfg = Release|Win32
+ {93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}.Release|Win32.Build.0 = Release|Win32
+ {7616C72B-2571-4DF9-8BF7-8B32359CE489}.Debug|Win32.ActiveCfg = Debug|Win32
+ {7616C72B-2571-4DF9-8BF7-8B32359CE489}.Debug|Win32.Build.0 = Debug|Win32
+ {7616C72B-2571-4DF9-8BF7-8B32359CE489}.Release|Win32.ActiveCfg = Release|Win32
+ {7616C72B-2571-4DF9-8BF7-8B32359CE489}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/trunk/rdptransport/rdppass.suo b/trunk/rdptransport/rdppass.suo
new file mode 100644
index 00000000..f282c90e
Binary files /dev/null and b/trunk/rdptransport/rdppass.suo differ
diff --git a/trunk/rdptransport/rdppass/ReadMe.txt b/trunk/rdptransport/rdppass/ReadMe.txt
new file mode 100644
index 00000000..1068cbaa
--- /dev/null
+++ b/trunk/rdptransport/rdppass/ReadMe.txt
@@ -0,0 +1,54 @@
+========================================================================
+ BIBLIOTECA DE VÍNCULOS DINÁMICOS: rdppass
+ Información general del proyecto
+========================================================================
+
+AppWizard ha creado este archivo DLL rdppass.
+
+Este archivo incluye un resumen acerca del contenido de los archivos que
+constituyen su aplicación rdppass.
+
+
+rdppass.vcxproj
+ Éste es el archivo de proyecto principal para los proyectos de VC++
+ generados mediante un Asistente para aplicaciones.
+ Contiene información acerca de la versión de Visual C++ con la que
+ se generó el archivo, así como información acerca de las plataformas,
+ configuraciones y características del proyecto seleccionadas en el
+ asistente para aplicaciones.
+
+rdppass.vcxproj.filters
+ Éste es el archivo de filtros para los proyectos de VC++ generados
+ mediante un asistente para aplicaciones.
+ Contiene información acerca de la asociación entre los archivos de
+ un proyecto y los filtros. Esta asociación se usa en el IDE para mostrar
+ la agrupación de archivos con extensiones similares bajo un nodo
+ específico (por ejemplo, los archivos ".cpp" se asocian con el filtro
+ "Archivos de código fuente").
+
+rdppass.cpp
+ Éste es el archivo de código fuente DLL principal.
+
+ Cuando se crea este archivo DLL, no exporta símbolos. Por lo tanto, no
+ creará un archivo .lib cuando se genere. Si desea que este proyecto sea
+ una dependencia de otro proyecto, es preciso agregar código para exportar
+ algunos símbolos del archivo DLL para crear una biblioteca de exportación,
+ o establecer la propiedad Omitir biblioteca de entrada en Sí en la página
+ de propiedades General en la carpeta Vinculador del cuadro de diálogo
+ Páginas de propiedades del proyecto.
+
+/////////////////////////////////////////////////////////////////////////////
+Otros archivos estándar:
+
+StdAfx.h, StdAfx.cpp
+ Estos archivos se utilizan para crear un archivo de encabezado precompilado
+ (PCH) denominado rdppass.pch y un archivo de tipos
+ precompilado denominado StdAfx.obj.
+
+/////////////////////////////////////////////////////////////////////////////
+Otras notas:
+
+El asistente para aplicaciones utiliza comentarios "TODO:" para indicar las
+partes del código fuente que tendrá que agregar o personalizar.
+
+/////////////////////////////////////////////////////////////////////////////
diff --git a/trunk/rdptransport/rdppass/dllmain.cpp b/trunk/rdptransport/rdppass/dllmain.cpp
new file mode 100644
index 00000000..8e7951c0
--- /dev/null
+++ b/trunk/rdptransport/rdppass/dllmain.cpp
@@ -0,0 +1,19 @@
+// dllmain.cpp : Define el punto de entrada de la aplicacin DLL.
+#include "stdafx.h"
+
+BOOL APIENTRY DllMain( HMODULE hModule,
+ DWORD ul_reason_for_call,
+ LPVOID lpReserved
+ )
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
diff --git a/trunk/rdptransport/rdppass/dpapi.cpp b/trunk/rdptransport/rdppass/dpapi.cpp
new file mode 100644
index 00000000..6aecb6b8
--- /dev/null
+++ b/trunk/rdptransport/rdppass/dpapi.cpp
@@ -0,0 +1,133 @@
+#include "stdafx.h"
+#include
+#include
+#include
+#include
+#include "net_sourceforge_jdpapi_DPAPI.h"
+
+void throwByName(JNIEnv *env, const char *name, const char *msg) {
+ jclass cls = env->FindClass(name);
+ /* if cls is NULL, an exception has already been thrown */
+ if (cls != NULL) {
+ env->ThrowNew(cls, msg);
+ }
+ /* free the local ref */
+ env->DeleteLocalRef(cls);
+ }
+
+void throwLastError(JNIEnv *env) {
+ DWORD errorCode = GetLastError();
+ LPVOID buf;
+
+ int messageFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ FormatMessage(messageFlags, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buf, 0, NULL);
+
+ throwByName(env, "net/sourceforge/jdpapi/DPAPIException", (char *) buf);
+ LocalFree(buf);
+ }
+
+DWORD getFlags(jboolean useLocalMachine) {
+ DWORD flags = CRYPTPROTECT_UI_FORBIDDEN;
+ if (useLocalMachine) {
+ flags |= CRYPTPROTECT_LOCAL_MACHINE;
+ }
+ return flags;
+}
+
+DATA_BLOB getBlobFromBytes(JNIEnv *env, jbyteArray bytes) {
+ DATA_BLOB result;
+
+ if (bytes != NULL) {
+ result.pbData = (BYTE *) env->GetByteArrayElements(bytes, JNI_FALSE);
+ result.cbData = (DWORD) env->GetArrayLength(bytes);
+ } else {
+ result.pbData = NULL;
+ result.cbData = 0;
+ }
+ return result;
+}
+
+void freeBytesFromBlob(JNIEnv *env, jbyteArray originalBytes, DATA_BLOB blob) {
+ if (originalBytes != NULL) {
+ env->ReleaseByteArrayElements(originalBytes, (jbyte*) blob.pbData, JNI_ABORT);
+ }
+}
+
+jstring getJStringFromBlob(JNIEnv *env, DATA_BLOB blob) {
+ char * str = new char[blob.cbData + 1];
+ memcpy(str, blob.pbData, blob.cbData);
+ str[blob.cbData] = NULL;
+
+ jstring result = env->NewStringUTF(str);
+
+ delete str;
+ LocalFree(blob.pbData);
+
+ return result;
+}
+
+jbyteArray getJByteArrayFromBlob(JNIEnv *env, DATA_BLOB blob) {
+ jbyteArray result = env->NewByteArray(blob.cbData);
+ env->SetByteArrayRegion(result, 0, blob.cbData, (jbyte *)blob.pbData);
+
+ LocalFree(blob.pbData);
+ return result;
+}
+
+DATA_BLOB getBlobFromJString(JNIEnv *env, jstring str) {
+ DATA_BLOB result;
+
+ const jchar *nativeString = env->GetStringChars(str, 0);
+ jsize len = env->GetStringLength(str);
+ result.pbData = (BYTE *) nativeString;
+ result.cbData = (DWORD)len*sizeof(wchar_t);
+
+ return result;
+}
+
+void freeJStringFromBlob(JNIEnv *env, jstring original, DATA_BLOB blob) {
+ env->ReleaseStringChars(original, (const jchar *) blob.pbData);
+}
+
+JNIEXPORT jbyteArray JNICALL Java_net_sourceforge_jdpapi_DPAPI_CryptProtectData
+ (JNIEnv *env, jclass clazz, jstring key, jbyteArray entropyBytes, jboolean useLocalMachine) {
+
+ DATA_BLOB output;
+ DATA_BLOB input = getBlobFromJString(env, key);
+ DATA_BLOB entropy = getBlobFromBytes(env, entropyBytes);
+
+// BOOL completed = CryptProtectData(&input, L"psw", &entropy, NULL, NULL, getFlags(useLocalMachine), &output);
+ BOOL completed = CryptProtectData(&input, L"psw", NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &output);
+
+ freeBytesFromBlob(env, entropyBytes, entropy);
+ freeJStringFromBlob(env, key, input);
+
+ if (!completed) {
+ throwLastError(env);
+ return NULL;
+ }
+
+ return getJByteArrayFromBlob(env, output);
+}
+
+
+JNIEXPORT jstring JNICALL Java_net_sourceforge_jdpapi_DPAPI_CryptUnprotectData
+ (JNIEnv *env, jclass clazz, jbyteArray data, jbyteArray entropyBytes) {
+
+ DATA_BLOB output;
+ DATA_BLOB input = getBlobFromBytes(env, data);
+ DATA_BLOB entropy = getBlobFromBytes(env, entropyBytes);
+
+ BOOL completed = CryptUnprotectData(&input, (LPWSTR *) NULL, &entropy, NULL, NULL, getFlags(JNI_FALSE), &output);
+
+ freeBytesFromBlob(env, entropyBytes, entropy);
+ freeBytesFromBlob(env, data, input);
+
+ if (!completed) {
+ throwLastError(env);
+ return NULL;
+ }
+
+ return getJStringFromBlob(env, output);
+}
\ No newline at end of file
diff --git a/trunk/rdptransport/rdppass/net_sourceforge_jdpapi_DPAPI.h b/trunk/rdptransport/rdppass/net_sourceforge_jdpapi_DPAPI.h
new file mode 100644
index 00000000..c36ca4dc
--- /dev/null
+++ b/trunk/rdptransport/rdppass/net_sourceforge_jdpapi_DPAPI.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class net_sourceforge_jdpapi_DPAPI */
+
+#ifndef _Included_net_sourceforge_jdpapi_DPAPI
+#define _Included_net_sourceforge_jdpapi_DPAPI
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: net_sourceforge_jdpapi_DPAPI
+ * Method: CryptProtectData
+ * Signature: (Ljava/lang/String;[BZ)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_net_sourceforge_jdpapi_DPAPI_CryptProtectData
+ (JNIEnv *, jclass, jstring, jbyteArray, jboolean);
+
+/*
+ * Class: net_sourceforge_jdpapi_DPAPI
+ * Method: CryptUnprotectData
+ * Signature: ([B[B)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_net_sourceforge_jdpapi_DPAPI_CryptUnprotectData
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/trunk/rdptransport/rdppass/rdppass.cpp b/trunk/rdptransport/rdppass/rdppass.cpp
new file mode 100644
index 00000000..633780da
--- /dev/null
+++ b/trunk/rdptransport/rdppass/rdppass.cpp
@@ -0,0 +1,6 @@
+// rdppass.cpp: define las funciones exportadas de la aplicacin DLL.
+//
+
+#include "stdafx.h"
+
+
diff --git a/trunk/rdptransport/rdppass/rdppass.vcxproj b/trunk/rdptransport/rdppass/rdppass.vcxproj
new file mode 100644
index 00000000..41714d2a
--- /dev/null
+++ b/trunk/rdptransport/rdppass/rdppass.vcxproj
@@ -0,0 +1,107 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {93D07375-5BA0-4EE5-BE8F-68B0DF187BFF}
+ Win32Proj
+ rdppass
+
+
+
+ DynamicLibrary
+ true
+ Unicode
+
+
+ DynamicLibrary
+ false
+ true
+ Unicode
+ Static
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+
+ Use
+ Level3
+ Disabled
+ WIN32;_DEBUG;_WINDOWS;_USRDLL;RDPPASS_EXPORTS;%(PreprocessorDefinitions)
+ C:\Program Files %28x86%29\Java\jdk1.5.0_22\include;C:\Program Files %28x86%29\Java\jdk1.5.0_22\include\win32;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ crypt32.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ Use
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_WINDOWS;_USRDLL;RDPPASS_EXPORTS;%(PreprocessorDefinitions)
+ C:\Program Files %28x86%29\Java\jdk1.5.0_22\include;C:\Program Files %28x86%29\Java\jdk1.5.0_22\include\win32;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ true
+ true
+ crypt32.lib;%(AdditionalDependencies)
+ LinkVerbose
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+ Create
+ Create
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/rdptransport/rdppass/rdppass.vcxproj.filters b/trunk/rdptransport/rdppass/rdppass.vcxproj.filters
new file mode 100644
index 00000000..915022ab
--- /dev/null
+++ b/trunk/rdptransport/rdppass/rdppass.vcxproj.filters
@@ -0,0 +1,45 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+
+
+
+ Archivos de encabezado
+
+
+ Archivos de encabezado
+
+
+ Archivos de encabezado
+
+
+
+
+ Archivos de código fuente
+
+
+ Archivos de código fuente
+
+
+ Archivos de código fuente
+
+
+ Archivos de código fuente
+
+
+
\ No newline at end of file
diff --git a/trunk/rdptransport/rdppass/rdppass.vcxproj.user b/trunk/rdptransport/rdppass/rdppass.vcxproj.user
new file mode 100644
index 00000000..695b5c78
--- /dev/null
+++ b/trunk/rdptransport/rdppass/rdppass.vcxproj.user
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/trunk/rdptransport/rdppass/stdafx.cpp b/trunk/rdptransport/rdppass/stdafx.cpp
new file mode 100644
index 00000000..4b41d38e
--- /dev/null
+++ b/trunk/rdptransport/rdppass/stdafx.cpp
@@ -0,0 +1,8 @@
+// stdafx.cpp: archivo de cdigo fuente que contiene slo las inclusiones estndar
+// rdppass.pch ser el encabezado precompilado
+// stdafx.obj contiene la informacin de tipos precompilada
+
+#include "stdafx.h"
+
+// TODO: mencionar los encabezados adicionales que se necesitan en STDAFX.H
+// pero no en este archivo
diff --git a/trunk/rdptransport/rdppass/stdafx.h b/trunk/rdptransport/rdppass/stdafx.h
new file mode 100644
index 00000000..355b4a8c
--- /dev/null
+++ b/trunk/rdptransport/rdppass/stdafx.h
@@ -0,0 +1,16 @@
+// stdafx.h: archivo de inclusin de los archivos de inclusin estndar del sistema
+// o archivos de inclusin especficos de un proyecto utilizados frecuentemente,
+// pero rara vez modificados
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#define WIN32_LEAN_AND_MEAN // Excluir material rara vez utilizado de encabezados de Windows
+// Archivos de encabezado de Windows:
+#include
+
+
+
+// TODO: mencionar aqu los encabezados adicionales que el programa necesita
diff --git a/trunk/rdptransport/rdppass/targetver.h b/trunk/rdptransport/rdppass/targetver.h
new file mode 100644
index 00000000..da80cd8f
--- /dev/null
+++ b/trunk/rdptransport/rdppass/targetver.h
@@ -0,0 +1,8 @@
+#pragma once
+
+// La inclusin de SDKDDKVer.h define la plataforma Windows ms alta disponible.
+
+// Si desea compilar la aplicacin para una plataforma Windows anterior, incluya WinSDKVer.h y
+// establezca la macro _WIN32_WINNT en la plataforma que desea admitir antes de incluir SDKDDKVer.h.
+
+#include
diff --git a/trunk/rdptransport/testPass/ReadMe.txt b/trunk/rdptransport/testPass/ReadMe.txt
new file mode 100644
index 00000000..d53c3e4b
--- /dev/null
+++ b/trunk/rdptransport/testPass/ReadMe.txt
@@ -0,0 +1,46 @@
+========================================================================
+ APLICACIÓN DE CONSOLA: testPass
+ Información general del proyecto
+========================================================================
+
+AppWizard ha creado esta aplicación testPass.
+
+Este archivo incluye un resumen acerca del contenido de los archivos que
+constituyen su aplicación testPass.
+
+
+testPass.vcxproj
+ Éste es el archivo de proyecto principal para los proyectos de VC++
+ generados mediante un Asistente para aplicaciones.
+ Contiene información acerca de la versión de Visual C++ con la que
+ se generó el archivo, así como información acerca de las plataformas,
+ configuraciones y características del proyecto seleccionadas en el
+ asistente para aplicaciones.
+
+testPass.vcxproj.filters
+ Éste es el archivo de filtros para los proyectos de VC++ generados
+ mediante un asistente para aplicaciones.
+ Contiene información acerca de la asociación entre los archivos de
+ un proyecto y los filtros. Esta asociación se usa en el IDE para mostrar
+ la agrupación de archivos con extensiones similares bajo un nodo
+ específico (por ejemplo, los archivos ".cpp" se asocian con el filtro
+ "Archivos de código fuente").
+
+testPass.cpp
+ Ésta es la aplicación principal del archivo de código fuente.
+
+/////////////////////////////////////////////////////////////////////////////
+Otros archivos estándar:
+
+StdAfx.h, StdAfx.cpp
+ Estos archivos se utilizan para crear un archivo de encabezado precompilado
+ (PCH) denominado testPass.pch y un archivo de tipos
+ precompilado denominado StdAfx.obj.
+
+/////////////////////////////////////////////////////////////////////////////
+Otras notas:
+
+El asistente para aplicaciones utiliza comentarios "TODO:" para indicar las
+partes del código fuente que tendrá que agregar o personalizar.
+
+/////////////////////////////////////////////////////////////////////////////
diff --git a/trunk/rdptransport/testPass/stdafx.cpp b/trunk/rdptransport/testPass/stdafx.cpp
new file mode 100644
index 00000000..e2bbbec2
--- /dev/null
+++ b/trunk/rdptransport/testPass/stdafx.cpp
@@ -0,0 +1,8 @@
+// stdafx.cpp: archivo de cdigo fuente que contiene slo las inclusiones estndar
+// testPass.pch ser el encabezado precompilado
+// stdafx.obj contiene la informacin de tipos precompilada
+
+#include "stdafx.h"
+
+// TODO: mencionar los encabezados adicionales que se necesitan en STDAFX.H
+// pero no en este archivo
diff --git a/trunk/rdptransport/testPass/stdafx.h b/trunk/rdptransport/testPass/stdafx.h
new file mode 100644
index 00000000..3e732adc
--- /dev/null
+++ b/trunk/rdptransport/testPass/stdafx.h
@@ -0,0 +1,15 @@
+// stdafx.h: archivo de inclusin de los archivos de inclusin estndar del sistema
+// o archivos de inclusin especficos de un proyecto utilizados frecuentemente,
+// pero rara vez modificados
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#include
+#include
+
+
+
+// TODO: mencionar aqu los encabezados adicionales que el programa necesita
diff --git a/trunk/rdptransport/testPass/targetver.h b/trunk/rdptransport/testPass/targetver.h
new file mode 100644
index 00000000..da80cd8f
--- /dev/null
+++ b/trunk/rdptransport/testPass/targetver.h
@@ -0,0 +1,8 @@
+#pragma once
+
+// La inclusin de SDKDDKVer.h define la plataforma Windows ms alta disponible.
+
+// Si desea compilar la aplicacin para una plataforma Windows anterior, incluya WinSDKVer.h y
+// establezca la macro _WIN32_WINNT en la plataforma que desea admitir antes de incluir SDKDDKVer.h.
+
+#include
diff --git a/trunk/rdptransport/testPass/testPass.cpp b/trunk/rdptransport/testPass/testPass.cpp
new file mode 100644
index 00000000..58178701
--- /dev/null
+++ b/trunk/rdptransport/testPass/testPass.cpp
@@ -0,0 +1,115 @@
+// testPass.cpp: define el punto de entrada de la aplicacin de consola.
+//
+
+#include "stdafx.h"
+#include
+#include
+#include
+
+void MyHandleError(char *s);
+
+char arr[] = { '0', '1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
+
+void byteToHex(BYTE data)
+{
+ int n1 = data & 0x0f;
+ int n2 = (data>>4) & 0x0f;
+ printf("%c%c", arr[n2], arr[n1]);
+
+}
+
+void bytesToHex(BYTE* data, size_t len)
+{
+ for( int i = 0; i < len; i++ )
+ byteToHex(data[i]);
+ printf("\n");
+}
+
+int _tmain(int argc, _TCHAR* argv[])
+{
+ wchar_t* pass = L"temporal";
+ printf("Size of wchar_t: %d\n", sizeof(wchar_t));
+ DATA_BLOB DataIn;
+ DATA_BLOB DataOut;
+ DATA_BLOB DataVerify;
+ BYTE *pbDataInput =(BYTE *)pass;
+ DWORD cbDataInput = 16;//strlen((char *)pbDataInput)+1;
+ DataIn.pbData = pbDataInput;
+ DataIn.cbData = cbDataInput;
+ //-------------------------------------------------------------------
+ // Begin processing.
+
+ printf("The data to be encrypted is: %s\n",pass);
+
+ //-------------------------------------------------------------------
+ // Begin protect phase.
+
+ if(CryptProtectData(
+ &DataIn,
+ L"psw", // A description string.
+ NULL, // Optional entropy
+ // not used.
+ NULL, // Reserved.
+ NULL, // Pass a PromptStruct.
+ CRYPTPROTECT_UI_FORBIDDEN,
+ &DataOut))
+ {
+ printf("The encryption phase worked. \n");
+ printf("Data len: %d\n", DataOut.cbData);
+ printf("01000000D08C9DDF0115D1118C7A00C04FC297EB01000000FE75A256A68D3C4C881421C753628A9800000000080000007000730077000000106600000001000020000000D2A595F857AC73031C75AA190BCEF52327E7FE2B51A35D6C6AC03703DE115714000000000E8000000002000020000000F1C8AC290D19EFE11EEAA10B6A9F933105ADC8C04526A139000E7F535F2FCF6E20000000104E256EF4ABD26A8A7F506938E2A23127D7F9EB1CFFEEB6356445043F1E02C140000000ACC013C6D269C09DEB9B951E952348C12A5259C7A475AF66ED3861D9F8D1D1057449761332A4B624905CD043FEEB45918E0AE26245801E5ED229C43DF7872876\n");
+ bytesToHex(DataOut.pbData, DataOut.cbData);
+ }
+ else
+ {
+ MyHandleError("Encryption error!");
+ }
+ //-------------------------------------------------------------------
+ // Begin unprotect phase.
+
+ if (CryptUnprotectData(
+ &DataOut,
+ NULL,
+ NULL, // Optional entropy
+ NULL, // Reserved
+ NULL, // Optional PromptStruct
+ CRYPTPROTECT_UI_FORBIDDEN,
+ &DataVerify))
+ {
+ printf("The decrypted data is: %s\n", DataVerify.pbData);
+ }
+ else
+ {
+ MyHandleError("Decryption error!");
+ }
+ //-------------------------------------------------------------------
+ // At this point, memcmp could be used to compare DataIn.pbData and
+ // DataVerify.pbDate for equality. If the two functions worked
+ // correctly, the two byte strings are identical.
+
+ //-------------------------------------------------------------------
+ // Clean up.
+
+ LocalFree(DataOut.pbData);
+ LocalFree(DataVerify.pbData);
+ char c;
+ scanf("%c", &c);
+} // End of main
+
+//-------------------------------------------------------------------
+// This example uses the function MyHandleError, a simple error
+// handling function, to print an error message to the
+// standard error (stderr) file and exit the program.
+// For most applications, replace this function with one
+// that does more extensive error reporting.
+
+void MyHandleError(char *s)
+{
+ fprintf(stderr,"An error occurred in running the program. \n");
+ fprintf(stderr,"%s\n",s);
+ fprintf(stderr, "Error number %x.\n", GetLastError());
+ fprintf(stderr, "Program terminating. \n");
+ exit(1);
+} // End of MyHandleError
+
+
+
diff --git a/trunk/rdptransport/testPass/testPass.vcxproj b/trunk/rdptransport/testPass/testPass.vcxproj
new file mode 100644
index 00000000..7c65e753
--- /dev/null
+++ b/trunk/rdptransport/testPass/testPass.vcxproj
@@ -0,0 +1,93 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {7616C72B-2571-4DF9-8BF7-8B32359CE489}
+ Win32Proj
+ testPass
+
+
+
+ Application
+ true
+ Unicode
+
+
+ Application
+ false
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+
+ Use
+ Level3
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+
+
+ Console
+ true
+ crypt32.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ Use
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+
+
+ Console
+ true
+ true
+ true
+ crypt32.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/rdptransport/testPass/testPass.vcxproj.filters b/trunk/rdptransport/testPass/testPass.vcxproj.filters
new file mode 100644
index 00000000..6ca300c9
--- /dev/null
+++ b/trunk/rdptransport/testPass/testPass.vcxproj.filters
@@ -0,0 +1,36 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+
+
+
+ Archivos de encabezado
+
+
+ Archivos de encabezado
+
+
+
+
+ Archivos de código fuente
+
+
+ Archivos de código fuente
+
+
+
\ No newline at end of file
diff --git a/trunk/rdptransport/testPass/testPass.vcxproj.user b/trunk/rdptransport/testPass/testPass.vcxproj.user
new file mode 100644
index 00000000..695b5c78
--- /dev/null
+++ b/trunk/rdptransport/testPass/testPass.vcxproj.user
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/trunk/server/.project b/trunk/server/.project
new file mode 100644
index 00000000..c72ed172
--- /dev/null
+++ b/trunk/server/.project
@@ -0,0 +1,18 @@
+
+
+ uds
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+ org.python.pydev.django.djangoNature
+
+
diff --git a/trunk/server/.pydevproject b/trunk/server/.pydevproject
new file mode 100644
index 00000000..f0c43afa
--- /dev/null
+++ b/trunk/server/.pydevproject
@@ -0,0 +1,16 @@
+
+
+
+
+python2.7
+python 2.7
+
+DJANGO_MANAGE_LOCATION
+src/manage.py
+DJANGO_SETTINGS_MODULE
+server.settings
+
+
+/uds/src
+
+
diff --git a/trunk/server/.settings/org.eclipse.core.resources.prefs b/trunk/server/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000..7394e283
--- /dev/null
+++ b/trunk/server/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,205 @@
+eclipse.preferences.version=1
+encoding//documentation/_downloads/samples/services/SampleProvider.py=utf-8
+encoding//documentation/_downloads/samples/services/SamplePublication.py=utf-8
+encoding//documentation/_downloads/samples/services/SampleService.py=utf-8
+encoding//documentation/_downloads/samples/services/SampleUserDeploymentOne.py=utf-8
+encoding//documentation/_downloads/samples/services/SampleUserDeploymentTwo.py=utf-8
+encoding//documentation/_downloads/samples/services/__init__.py=utf-8
+encoding//documentation/conf.py=utf-8
+encoding//src/server/settings.py=utf-8
+encoding//src/server/urls.py=utf-8
+encoding//src/uds/__init__.py=utf-8
+encoding//src/uds/auths/ActiveDirectory/Authenticator.py=utf-8
+encoding//src/uds/auths/ActiveDirectory/__init__.py=utf-8
+encoding//src/uds/auths/ActiveDirectory_enterprise/Authenticator.py=utf-8
+encoding//src/uds/auths/Dummy/Authenticator.py=utf-8
+encoding//src/uds/auths/Dummy/__init__.py=utf-8
+encoding//src/uds/auths/EDirectory/Authenticator.py=utf-8
+encoding//src/uds/auths/EDirectory/__init__.py=utf-8
+encoding//src/uds/auths/EDirectory_enterprise/Authenticator.py=utf-8
+encoding//src/uds/auths/IP/Authenticator.py=utf-8
+encoding//src/uds/auths/IP/__init__.py=utf-8
+encoding//src/uds/auths/InternalDB/Authenticator.py=utf-8
+encoding//src/uds/auths/InternalDB/__init__.py=utf-8
+encoding//src/uds/auths/RegexLdap/Authenticator.py=utf-8
+encoding//src/uds/auths/RegexLdap/__init__.py=utf-8
+encoding//src/uds/auths/SAML/SAML.py=utf-8
+encoding//src/uds/auths/SAML/__init__.py=utf-8
+encoding//src/uds/auths/SAML_enterprise/SAML.py=utf-8
+encoding//src/uds/auths/SAML_enterprise/__init__.py=utf-8
+encoding//src/uds/auths/Sample/SampleAuth.py=utf-8
+encoding//src/uds/auths/Sample/__init__.py=utf-8
+encoding//src/uds/auths/SimpleLDAP/Authenticator.py=utf-8
+encoding//src/uds/auths/SimpleLDAP/__init__.py=utf-8
+encoding//src/uds/auths/__init__.py=utf-8
+encoding//src/uds/core/BaseModule.py=utf-8
+encoding//src/uds/core/Environment.py=utf-8
+encoding//src/uds/core/Serializable.py=utf-8
+encoding//src/uds/core/__init__.py=utf-8
+encoding//src/uds/core/auths/AuthsFactory.py=utf-8
+encoding//src/uds/core/auths/BaseAuthenticator.py=utf-8
+encoding//src/uds/core/auths/Exceptions.py=utf-8
+encoding//src/uds/core/auths/Group.py=utf-8
+encoding//src/uds/core/auths/GroupsManager.py=utf-8
+encoding//src/uds/core/auths/User.py=utf-8
+encoding//src/uds/core/auths/__init__.py=utf-8
+encoding//src/uds/core/auths/auth.py=utf-8
+encoding//src/uds/core/jobs/DelayedTask.py=utf-8
+encoding//src/uds/core/jobs/DelayedTaskRunner.py=utf-8
+encoding//src/uds/core/jobs/Job.py=utf-8
+encoding//src/uds/core/jobs/JobsFactory.py=utf-8
+encoding//src/uds/core/jobs/Scheduler.py=utf-8
+encoding//src/uds/core/jobs/__init__.py=utf-8
+encoding//src/uds/core/managers/CryptoManager.py=utf-8
+encoding//src/uds/core/managers/DownloadsManager.py=utf-8
+encoding//src/uds/core/managers/LogManager.py=utf-8
+encoding//src/uds/core/managers/PublicationManager.py=utf-8
+encoding//src/uds/core/managers/TaskManager.py=utf-8
+encoding//src/uds/core/managers/UserPrefsManager.py=utf-8
+encoding//src/uds/core/managers/UserServiceManager.py=utf-8
+encoding//src/uds/core/managers/__init__.py=utf-8
+encoding//src/uds/core/osmanagers/BaseOsManager.py=utf-8
+encoding//src/uds/core/osmanagers/OSManagersFactory.py=utf-8
+encoding//src/uds/core/osmanagers/__init__.py=utf-8
+encoding//src/uds/core/services/BaseDeployed.py=utf-8
+encoding//src/uds/core/services/BasePublication.py=utf-8
+encoding//src/uds/core/services/BaseService.py=utf-8
+encoding//src/uds/core/services/BaseServiceProvider.py=utf-8
+encoding//src/uds/core/services/Exceptions.py=utf-8
+encoding//src/uds/core/services/ServiceProviderFactory.py=utf-8
+encoding//src/uds/core/services/__init__.py=utf-8
+encoding//src/uds/core/transports/BaseTransport.py=utf-8
+encoding//src/uds/core/transports/TransportsFactory.py=utf-8
+encoding//src/uds/core/transports/__init__.py=utf-8
+encoding//src/uds/core/transports/protocols.py=utf-8
+encoding//src/uds/core/ui/UserInterface.py=utf-8
+encoding//src/uds/core/util/AutoAttributes.py=utf-8
+encoding//src/uds/core/util/Cache.py=utf-8
+encoding//src/uds/core/util/Config.py=utf-8
+encoding//src/uds/core/util/OsDetector.py=utf-8
+encoding//src/uds/core/util/State.py=utf-8
+encoding//src/uds/core/util/StateQueue.py=utf-8
+encoding//src/uds/core/util/Storage.py=utf-8
+encoding//src/uds/core/util/UniqueIDGenerator.py=utf-8
+encoding//src/uds/core/util/UniqueMacGenerator.py=utf-8
+encoding//src/uds/core/util/UniqueNameGenerator.py=utf-8
+encoding//src/uds/core/util/connection.py=utf-8
+encoding//src/uds/core/util/log.py=utf-8
+encoding//src/uds/core/util/modfinder.py=utf-8
+encoding//src/uds/core/workers/AssignedAndUnused.py=utf-8
+encoding//src/uds/core/workers/CacheCleaner.py=utf-8
+encoding//src/uds/core/workers/DeployedServiceCleaner.py=utf-8
+encoding//src/uds/core/workers/PublicationCleaner.py=utf-8
+encoding//src/uds/core/workers/ServiceCacheUpdater.py=utf-8
+encoding//src/uds/core/workers/UserServiceCleaner.py=utf-8
+encoding//src/uds/dispatchers/__init__.py=utf-8
+encoding//src/uds/dispatchers/pam/urls.py=utf-8
+encoding//src/uds/dispatchers/pam/views.py=utf-8
+encoding//src/uds/dispatchers/test/urls.py=utf-8
+encoding//src/uds/dispatchers/test/views.py=utf-8
+encoding//src/uds/dispatchers/wyse_enterprise/__init__.py=utf-8
+encoding//src/uds/dispatchers/wyse_enterprise/urls.py=utf-8
+encoding//src/uds/dispatchers/wyse_enterprise/views.py=utf-8
+encoding//src/uds/management/commands/config.py=utf-8
+encoding//src/uds/management/commands/taskManager.py=utf-8
+encoding//src/uds/management/commands/taskManager_enterprise.py=utf-8
+encoding//src/uds/migrations/0001_initial.py=utf-8
+encoding//src/uds/migrations/0002_auto__del_unique_userpreference_name_module.py=utf-8
+encoding//src/uds/migrations/0004_auto__add_field_deployedservice_state_date.py=utf-8
+encoding//src/uds/migrations/0005_auto__add_field_config_crypt.py=utf-8
+encoding//src/uds/migrations/0008_auto__add_userservicelog__add_field_userservice_src_hostname__add_fiel.py=utf-8
+encoding//src/uds/migrations/0009_auto__del_userservicelog__add_log.py=utf-8
+encoding//src/uds/migrations/0010_auto__add_field_log_owner_type.py=utf-8
+encoding//src/uds/models.py=utf-8
+encoding//src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py=utf-8
+encoding//src/uds/osmanagers/LinuxOsManager/__init__.py=utf-8
+encoding//src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py=utf-8
+encoding//src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py=utf-8
+encoding//src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py=utf-8
+encoding//src/uds/osmanagers/WindowsOsManager/__init__.py=utf-8
+encoding//src/uds/osmanagers/__init__.py=utf-8
+encoding//src/uds/services/OVirt/OVirtLinkedDeployment.py=utf-8
+encoding//src/uds/services/OVirt/OVirtLinkedService.py=utf-8
+encoding//src/uds/services/OVirt/OVirtProvider.py=utf-8
+encoding//src/uds/services/OVirt/OVirtPublication.py=utf-8
+encoding//src/uds/services/OVirt/__init__.py=utf-8
+encoding//src/uds/services/PhysicalMachines/IPMachineDeployed.py=utf-8
+encoding//src/uds/services/PhysicalMachines/IPMachinesService.py=utf-8
+encoding//src/uds/services/PhysicalMachines/ServiceProvider.py=utf-8
+encoding//src/uds/services/PhysicalMachines/__init__.py=utf-8
+encoding//src/uds/services/Sample/SampleProvider.py=utf-8
+encoding//src/uds/services/Sample/SamplePublication.py=utf-8
+encoding//src/uds/services/Sample/SampleService.py=utf-8
+encoding//src/uds/services/Sample/SampleUserDeploymentOne.py=utf-8
+encoding//src/uds/services/Sample/SampleUserDeploymentTwo.py=utf-8
+encoding//src/uds/services/Sample/__init__.py=utf-8
+encoding//src/uds/services/Vmware/Helpers.py=utf-8
+encoding//src/uds/services/Vmware/PublicationVC.py=utf-8
+encoding//src/uds/services/Vmware/ServiceProviderVC.py=utf-8
+encoding//src/uds/services/Vmware/VCLinkedCloneDeployed.py=utf-8
+encoding//src/uds/services/Vmware/VCLinkedCloneService.py=utf-8
+encoding//src/uds/services/Vmware/__init__.py=utf-8
+encoding//src/uds/services/Vmware/client/Client.py=utf-8
+encoding//src/uds/services/Vmware/client/Exceptions.py=utf-8
+encoding//src/uds/services/Vmware/client/Server.py=utf-8
+encoding//src/uds/services/Vmware/client/Task.py=utf-8
+encoding//src/uds/services/Vmware/client/ws/VimService_types.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/Helpers.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/PublicationVC.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/ServiceProviderVC.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/VCLinkedCloneDeployed.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/VCLinkedCloneService.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/__init__.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/client/Client.py=utf-8
+encoding//src/uds/services/Vmware_enterprise/client/Server.py=utf-8
+encoding//src/uds/services/__init__.py=utf-8
+encoding//src/uds/tests.py=utf-8
+encoding//src/uds/transports/NX/NXTransport.py=utf-8
+encoding//src/uds/transports/NX/__init__.py=utf-8
+encoding//src/uds/transports/NX/web.py=utf-8
+encoding//src/uds/transports/RDP/RDPTransport.py=utf-8
+encoding//src/uds/transports/RDP/TSRDPTransport.py=utf-8
+encoding//src/uds/transports/RDP/__init__.py=utf-8
+encoding//src/uds/transports/RDP/web.py=utf-8
+encoding//src/uds/transports/RGS-enterprise/RGSTransport.py=utf-8
+encoding//src/uds/transports/RGS-enterprise/TRGSTransport.py=utf-8
+encoding//src/uds/transports/RGS-enterprise/__init__.py=utf-8
+encoding//src/uds/transports/RGS-enterprise/web.py=utf-8
+encoding//src/uds/transports/RGS/RGSTransport.py=utf-8
+encoding//src/uds/transports/RGS/TRGSTransport.py=utf-8
+encoding//src/uds/transports/RGS/__init__.py=utf-8
+encoding//src/uds/transports/RGS/web.py=utf-8
+encoding//src/uds/transports/TSNX/TSNXTransport.py=utf-8
+encoding//src/uds/transports/TSNX/__init__.py=utf-8
+encoding//src/uds/transports/TSNX/web.py=utf-8
+encoding//src/uds/transports/__init__.py=utf-8
+encoding//src/uds/urls.py=utf-8
+encoding//src/uds/urls_enterprise.py=utf-8
+encoding//src/uds/views.py=utf-8
+encoding//src/uds/web/errors.py=utf-8
+encoding//src/uds/web/forms/LoginForm.py=utf-8
+encoding//src/uds/web/transformers.py=utf-8
+encoding//src/uds/web/views.py=utf-8
+encoding//src/uds/xmlrpc/__init__.py=utf-8
+encoding//src/uds/xmlrpc/actor/Actor.py=utf-8
+encoding//src/uds/xmlrpc/auths/AdminAuth.py=utf-8
+encoding//src/uds/xmlrpc/auths/Authenticators.py=utf-8
+encoding//src/uds/xmlrpc/auths/Groups.py=utf-8
+encoding//src/uds/xmlrpc/auths/UserPreferences.py=utf-8
+encoding//src/uds/xmlrpc/auths/Users.py=utf-8
+encoding//src/uds/xmlrpc/log/logs.py=utf-8
+encoding//src/uds/xmlrpc/osmanagers/OSManagers.py=utf-8
+encoding//src/uds/xmlrpc/services/DeployedServices.py=utf-8
+encoding//src/uds/xmlrpc/services/Publications.py=utf-8
+encoding//src/uds/xmlrpc/services/ServiceProviders.py=utf-8
+encoding//src/uds/xmlrpc/services/Services.py=utf-8
+encoding//src/uds/xmlrpc/services/UserDeployedServices.py=utf-8
+encoding//src/uds/xmlrpc/tools/Cache.py=utf-8
+encoding//src/uds/xmlrpc/tools/Config.py=utf-8
+encoding//src/uds/xmlrpc/transports/Networks.py=utf-8
+encoding//src/uds/xmlrpc/transports/Transports.py=utf-8
+encoding//src/uds/xmlrpc/util/Callbacks.py=utf-8
+encoding//src/uds/xmlrpc/util/Exceptions.py=utf-8
+encoding//src/uds/xmlrpc/util/Helpers.py=utf-8
+encoding//src/uds/xmlrpc/util/TestTransport.py=utf-8
+encoding//src/uds/xmlrpc/views.py=utf-8
diff --git a/trunk/server/.settings/org.eclipse.ltk.core.refactoring.prefs b/trunk/server/.settings/org.eclipse.ltk.core.refactoring.prefs
new file mode 100644
index 00000000..6599c071
--- /dev/null
+++ b/trunk/server/.settings/org.eclipse.ltk.core.refactoring.prefs
@@ -0,0 +1,3 @@
+#Wed Jun 15 12:44:29 CEST 2011
+eclipse.preferences.version=1
+org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
diff --git a/trunk/server/.settings/org.eclipse.mylyn.tasks.ui.prefs b/trunk/server/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 00000000..0f638bd2
--- /dev/null
+++ b/trunk/server/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jan 03 02:43:56 CET 2012
+eclipse.preferences.version=1
+project.repository.kind=mantis
+project.repository.url=http\://192.168.0.6/mantis
diff --git a/trunk/server/.settings/org.eclipse.wst.css.core.prefs b/trunk/server/.settings/org.eclipse.wst.css.core.prefs
new file mode 100644
index 00000000..aaad38b5
--- /dev/null
+++ b/trunk/server/.settings/org.eclipse.wst.css.core.prefs
@@ -0,0 +1,3 @@
+#Thu Jan 19 07:33:49 CET 2012
+css-profile//src/uds/templates/uds/base.html=org.eclipse.wst.css.core.cssprofile.css3
+eclipse.preferences.version=1
diff --git a/trunk/server/.settings/org.eclipse.wst.html.core.prefs b/trunk/server/.settings/org.eclipse.wst.html.core.prefs
new file mode 100644
index 00000000..86d79db5
--- /dev/null
+++ b/trunk/server/.settings/org.eclipse.wst.html.core.prefs
@@ -0,0 +1,3 @@
+#Thu Jan 19 07:33:49 CET 2012
+document-type//src/uds/templates/uds/base.html=-//W3C//DTD XHTML 1.0 Transitional//EN
+eclipse.preferences.version=1
diff --git a/trunk/server/documentation/Makefile b/trunk/server/documentation/Makefile
new file mode 100644
index 00000000..8cbfba74
--- /dev/null
+++ b/trunk/server/documentation/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER = a4
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/UDS.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/UDS.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/UDS"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/UDS"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/trunk/server/documentation/_downloads/samples/auths/SampleAuth.py b/trunk/server/documentation/_downloads/samples/auths/SampleAuth.py
new file mode 100644
index 00000000..7b3699b8
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/auths/SampleAuth.py
@@ -0,0 +1,307 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as translatable
+from uds.core.ui.UserInterface import gui
+from uds.core import auths
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleAuth(auths.Authenticator):
+ '''
+ This class represents a sample authenticator.
+
+ As this, it will provide:
+ * The authenticator functionality
+ * 3 Groups, "Mortals", "Gods" and "Daemons", just random group names selected.. :-),
+ plus groups that we enter at Authenticator form, from admin interface.
+ * Search of groups (inside the 3 groups used in this sample plus entered)
+ * Search for people (will return the search string + 000...999 as usernames)
+ * The Required form description for administration interface, so admins can create
+ new authenticators of this kind.
+
+ In this sample, we will provide a simple standard auth, with owner drawn
+ login form that will simply show users that has been created and allow web user
+ to select one of them.
+
+ For this class to get visible at administration client as a authenticator type,
+ we MUST register it at package __init__
+
+ :note: At class level, the translations must be simply marked as so
+ using ugettext_noop. This is done in this way because we will translate
+ the string when it is sent to the administration client.
+ '''
+
+ #: Name of type, used at administration interface to identify this
+ #: authenticator (i.e. LDAP, SAML, ...)
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeName = translatable('Sample Authenticator')
+
+ #: Name of type used by Managers to identify this type of service
+ #: We could have used here the Class name, but we decided that the
+ #: module implementator will be the one that will provide a name that
+ #: will relation the class (type) and that name.
+ typeType = 'SampleAuthenticator'
+
+ #: Description shown at administration level for this authenticator.
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeDescription = translatable('Sample dummy authenticator')
+
+
+ #: Icon file, used to represent this authenticator at administration interface
+ #: This file should be at same folder as this class is, except if you provide
+ #: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
+ iconFile = 'auth.png'
+
+ #: Mark this authenticator as that the users comes from outside the UDS
+ #: database, that are most authenticator (except Internal DB)
+ #: True is the default value, so we do not need it in fact
+ # isExternalSource = True
+
+ #: If we need to enter the password for this user when creating a new
+ #: user at administration interface. Used basically by internal authenticator.
+ #: False is the default value, so this is not needed in fact
+ #: needsPassword = False
+
+ #: Label for username field, shown at administration interface user form.
+ userNameLabel = translatable('Fake User')
+
+ # Label for group field, shown at administration interface user form.
+ groupNameLabel = translatable('Fake Group')
+
+ #: Definition of this type of authenticator form
+ #: We will define a simple form where we will use a simple
+ #: list editor to allow entering a few group names
+
+ groups = gui.EditableList(label=translatable('Groups'), values = ['Gods', 'Daemons', 'Mortals'])
+
+ def initialize(self, values):
+ '''
+ Simply check if we have
+ at least one group in the list
+ '''
+
+ # To avoid problems, we only check data if values are passed
+ # If values are not passed in, form data will only be available after
+ # unserialization, and at this point all will be default values
+ # so self.groups.value will be []
+ if values is not None and len(self.groups.value) < 2:
+ raise auths.Authenticator.ValidationException(translatable('We need more that two items!'))
+
+ def searchUsers(self, pattern):
+ '''
+ Here we will receive a pattern for searching users.
+
+ This method is invoked from interface, so an administrator can search users.
+
+ If we do not provide this method, the authenticator will not provide search
+ facility for users. In our case, we will simply return a list of users
+ (array of dictionaries with ids and names) with the pattern plus 1..10
+ '''
+ return [ { 'id' : '{0}-{1}'.format(pattern, a), 'name' : '{0} number {1}'.format(pattern, a) } for a in range(1, 10)]
+
+ def searchGroups(self, pattern):
+ '''
+ Here we we will receive a patter for searching groups.
+
+ In this sample, we will try to locate elements that where entered at
+ sample authenticator form (when created), and return the ones that
+ contains the pattern indicated.
+ '''
+ pattern = pattern.lower()
+ res = []
+ for g in self.groups.value:
+ if g.lower().find(pattern) != -1:
+ res.append({'id' : g, 'name' : ''})
+ return res
+
+ def authenticate(self, username, credentials, groupsManager):
+ '''
+ This method is invoked by UDS whenever it needs an user to be authenticated.
+ It is used from web interface, but also from administration interface to
+ check credentials and access of user.
+
+ The tricky part of this method is the groupsManager, but it's easy to
+ understand what is used it for.
+
+ Imagine some authenticator, for example, an LDAP. It has its users, it has
+ its groups, and it has it relations (which user belongs to which group).
+
+ Now think about UDS. UDS know nothing about this, it only knows what
+ the administator has entered at admin interface (groups mainly, but he can
+ create users also).
+
+ UDS knows about this groups, but we need to relation those with the ones
+ know by the authenticator.
+
+ To do this, we have created a simple mechanism, where the authenticator
+ receives a groupsManager, that knows all groups known by UDS, and has
+ the method so the authenticator can say, for the username being validated,
+ to which uds groups it belongs to.
+
+ This is done using the :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
+ method of the provided groups manager.
+
+ At return, UDS will do two things:
+ * If there is no group inside the groupsManager mareked as valid, it will
+ denied access.
+ * If there is some groups marked as valid, it will refresh the known
+ UDS relations (this means that the database will be refresehd so the user
+ has valid groups).
+
+ This also means that the group membership is only checked at user login (well,
+ in fact its also checked when an administrator tries to modify an user)
+
+ So, authenticate must not also validate the user credentials, but also
+ indicate the group membership of this user inside UDS.
+
+ :note: groupsManager is an in/out parameter
+ '''
+ if username != credentials: # All users with same username and password are allowed
+ return False
+
+ # Now the tricky part. We will make this user belong to groups that contains at leat
+ # two letters equals to the groups names known by UDS
+ # For this, we will ask the groups manager for the groups names, and will check that and,
+ # if the user match this criteria, will mark that group as valid
+ for g in groupsManager.getGroupsNames():
+ if len(set(g.lower()).intersection(username.lower())) >= 2:
+ groupsManager.validate(g)
+
+ return True
+
+ def getGroups(self, username, groupsManager):
+ '''
+ As with authenticator part related to groupsManager, this
+ method will fill the groups to which the specified username belongs to.
+
+ We have to fill up groupsManager from two different places, so it's not
+ a bad idea to make a method that get the "real" authenticator groups and
+ them simply call to :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
+
+ In our case, we simply repeat the process that we also do at authenticate
+ '''
+ for g in groupsManager.getGroupsNames():
+ if len(set(g.lower()).intersection(username.lower())) >= 2:
+ groupsManager.validate(g)
+
+ def getHtml(self, request):
+ '''
+ If we override this method from the base one, we are telling UDS
+ that we want to draw our own authenticator.
+
+ This way, we can do whataver we want here (for example redirect to a site
+ for a single sign on) generation our ouwn html (and javascript ofc).
+
+ '''
+ # Here there is a sample, commented out
+ # In this sample, we will make a list of valid users, and when clicked,
+ # it will fill up original form with username and same password, and submit it.
+ #res = ''
+ #for u in self.dbAuthenticator().users.all():
+ # res += '{0}
'.format(u.name)
+ #
+ #res += ''
+ #return res
+
+ # I know, this is a bit ugly, but this is just a sample :-)
+
+ res = 'Login name:
'
+ res +='Login
'
+ return res
+
+
+ def authCallback(self, parameters):
+ '''
+ We provide this as a sample of callback for an user.
+ We will accept all petitions that has "user" parameter
+
+ This method will get invoked by url redirections, probably by an SSO.
+
+ The idea behind this is that we can provide:
+ * Simple user/password authentications
+ * Own authentications (not UDS, authenticator "owned"), but with no redirections
+ * Own authentications via redirections (as most SSO will do)
+
+ Here, we will receive the parameters for this
+ '''
+ user = parameters.get('user', None)
+
+ return user
+
+ def createUser(self, usrData):
+ '''
+ This method provides a "check oportunity" to authenticators for users created
+ manually at administration interface.
+
+ If we do not provide this method, the administration interface will not allow
+ to create new users "by hand", i mean, the "new" options from menus will dissapear.
+
+ usrData is a dictionary that contains the input parameters from user,
+ with at least name, realName, comments, state & password.
+
+ We can modify this parameters, we can modify ALL, but name is not recommended to
+ modify it unles you know what you are doing.
+
+ Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
+ '''
+ from uds.core.util.State import State
+ usrData['realName'] = usrData['name'] + ' ' + usrData['name']
+ usrData['state'] = State.INACTIVE
+
+ def modifyUser(self, usrData):
+ '''
+ This method provides a "check opportunity" to authenticator for users modified
+ at administration interface.
+
+ If we do not provide this method, nothing will happen (default one does nothing, but
+ it's valid).
+
+ usrData is a dictionary that contains the input parameters from user,
+ with at least name, realName, comments, state & password.
+
+ We can modify this parameters, we can modify ALL, but name is not recommended to
+ modify it unless you know what you are doing.
+
+ Here, we will simply update the realName of the user, and (we have to take care
+ this this kind of things) modify the userName to a new one, the original plus '-1'
+ '''
+ usrData['realName'] = usrData['name'] + ' ' + usrData['name']
+ usrData['name'] = usrData['name'] + '-1'
diff --git a/trunk/server/documentation/_downloads/samples/auths/__init__.py b/trunk/server/documentation/_downloads/samples/auths/__init__.py
new file mode 100644
index 00000000..548fde41
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/auths/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+Dummy test authenticator. Not used in production (it doesn't registers itself)
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core import auths
+from SampleAuth import SampleAuth
+
+# Commented, this auth exists for testing purposes only
+auths.factory().insert(SampleAuth)
diff --git a/trunk/server/documentation/_downloads/samples/auths/auth.png b/trunk/server/documentation/_downloads/samples/auths/auth.png
new file mode 100644
index 00000000..c8560b1d
Binary files /dev/null and b/trunk/server/documentation/_downloads/samples/auths/auth.png differ
diff --git a/trunk/server/documentation/_downloads/samples/services/SampleProvider.py b/trunk/server/documentation/_downloads/samples/services/SampleProvider.py
new file mode 100644
index 00000000..4f38368f
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/SampleProvider.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Created on Jun 22, 2012
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as translatable, ugettext as _
+from uds.core.services import ServiceProvider
+from SampleService import ServiceOne, ServiceTwo
+from uds.core.ui import gui
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class Provider(ServiceProvider):
+ '''
+ This class represents the sample services provider
+
+ In this class we provide:
+ * The Provider functionality
+ * The basic configuration parameters for the provider
+ * The form fields needed by administrators to configure this provider
+
+ :note: At class level, the translation must be simply marked as so
+ using ugettext_noop. This is so cause we will translate the string when
+ sent to the administration client.
+
+ For this class to get visible at administration client as a provider type,
+ we MUST register it at package __init__.
+
+ '''
+ #: What kind of services we offer, this are classes inherited from Service
+ offers = [ServiceOne, ServiceTwo]
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ typeName = translatable('Sample Provider')
+ #: Type used internally to identify this provider
+ typeType = 'SampleProvider'
+ #: Description shown at administration interface for this provider
+ typeDescription = translatable('Sample (and dummy) service provider')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ iconFile = 'provider.png'
+
+ # now comes the form fields
+ # There is always two fields that are requested to the admin, that are:
+ # Service Name, that is a name that the admin uses to name this provider
+ # Description, that is a short description that the admin gives to this provider
+ # Now we are going to add a few fields that we need to use this provider
+ # Remember that these are "dummy" fields, that in fact are not required
+ # but used for sample purposes
+ # If we don't indicate an order, the output order of fields will be
+ # "random"
+
+ #: Remote host. Here core will translate label and tooltip, remember to
+ #: mark them as translatable using ugettext_noop.
+ remoteHost = gui.TextField(oder=1,
+ length = 64,
+ label = translatable('Remote host'),
+ tooltip = translatable('This fields contains a remote host'),
+ required = True,
+ )
+ #: Name of your pet (sample, not really needed :-) )
+ petName = gui.TextField(order=2,
+ length = 32,
+ label = translatable('Your pet\'s name'),
+ tooltip = translatable('If you like, write the name of your pet'),
+ requred = False,
+ defvalue = 'Tux' #: This will not get translated
+ )
+ #: Age of Methuselah (matusalén in spanish)
+ #: in Spain there is a well-known to say that something is very old,
+ #: "Tiene mas años que matusalén"(is older than Methuselah)
+ methAge = gui.NumericField(order = 3,
+ length = 4, # That is, max allowed value is 9999
+ label = translatable('Age of Methuselah'),
+ tooltip = translatable('If you know it, please, tell me!!!'),
+ required = True, #: Numeric fields have always a value, so this not really needed
+ defvalue = '4500'
+ )
+
+ #: Is Methuselah istill alive?
+ methAlive = gui.CheckBoxField(order = 4,
+ label = translatable('Is Methuselah still alive?'),
+ tooltip = translatable('If you fails, this will not get saved :-)'),
+ required = True, #: Also means nothing. Check boxes has always a value
+ defvalue = gui.TRUE #: By default, at new item, check this
+ )
+
+ # There is more fields type, but not here the best place to cover it
+ def initialize(self, values = None):
+ '''
+ We will use the "autosave" feature for form fields, that is more than
+ enought for most providers. (We simply need to store data provided by user
+ and, maybe, initialize some kind of connection with this values).
+
+ Normally provider values are rally used at sevice level, cause we never
+ instantiate nothing except a service from a provider.
+ '''
+
+ # If you say meth is alive, you are wrong!!! (i guess..)
+ # values are only passed from administration client. Internals
+ # instantiations are always empty.
+ if values is not None and self.methAlive.isTrue():
+ raise ServiceProvider.ValidationException(_('Methuselah is not alive!!! :-)'))
+
+ # Marshal and unmarshal are defaults ones, also enought
+
+ # As we use "autosave" fields feature, dictValues is also provided by
+ # base class so we don't have to mess with all those things...
+
+ @staticmethod
+ def test(env, data):
+ '''
+ Create your test method here so the admin can push the "check" button
+ and this gets executed.
+ Args:
+ env: environment passed for testing (temporal environment passed)
+
+ data: data passed for testing (data obtained from the form
+ definition)
+
+ Returns:
+ Array of two elements, first is True of False, depending on test
+ (True is all right, false is error),
+ second is an String with error, preferably internacionalizated..
+
+ In this case, wi well do nothing more that use the provider params
+
+ Note also that this is an static method, that will be invoked using
+ the admin user provided data via administration client, and a temporary
+ environment that will be erased after invoking this method
+ '''
+ try:
+ # We instantiate the provider, but this may fail...
+ instance = Provider(env, data)
+ logger.debug('Methuselah has {0} years and is {1} :-)'
+ .format(instance.methAge.value, instance.methAlive.value))
+ except ServiceProvider.ValidationException as e:
+ # If we say that meth is alive, instantiation will
+ return [False, str(e)]
+ except Exception as e:
+ logger.exception("Exception caugth!!!")
+ return [False, str(e)]
+ return [True, _('Nothing tested, but all went fine..')]
+
+ # Congratulations!!!, the needed part of your first simple provider is done!
+ # Now you can go to administration panel, and check it
+ #
+ # From now onwards, we implement our own methods, that will be used by,
+ # for example, services derived from this provider
+ def host(self):
+ '''
+ Sample method, in fact in this we just return
+ the value of host field, that is an string
+ '''
+ return self.remoteHost.value
+
+
+ def methYears(self):
+ '''
+ Another sample return, it will in fact return the Methuselah years
+ '''
diff --git a/trunk/server/documentation/_downloads/samples/services/SamplePublication.py b/trunk/server/documentation/_downloads/samples/services/SamplePublication.py
new file mode 100644
index 00000000..f9ccfd5b
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/SamplePublication.py
@@ -0,0 +1,272 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.services import Publication
+from uds.core.util.State import State
+from datetime import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SamplePublication(Publication):
+ '''
+ This class shows how a publication is developed.
+
+ In order to a publication to work correctly, we must provide at least the
+ following methods:
+ * Of course, the __init__
+ * :py:meth:`.publish`
+ * :py:meth:`.checkState`
+ * :py:meth:`.finish`
+
+ Also, of course, methods from :py:class:`uds.core.Serializable.Serializable`
+
+
+ Publication do not have an configuration interface, all data contained
+ inside an instance of a Publication must be serialized if you want them between
+ method calls.
+
+ It's not waranteed that the class will not be serialized/deserialized
+ between methods calls, so, first of all, implement the marshal and umnarshal
+ mehods needed by all serializable classes.
+
+ Also a thing to note is that operations requested to Publications must be
+ *as fast as posible*. The operations executes in a separated thread,
+ and so it cant take a bit more time to execute, but it's recommended that
+ the operations executes as fast as posible, and, if it will take a long time,
+ split operation so we can keep track of state.
+
+ This means that, if we have "slow" operations, we must
+
+ We first of all declares an estimation of how long a publication will take.
+ This value is instance based, so if we override it in our class, the suggested
+ time could change.
+
+ The class attribute that indicates this suggested time is "suggestedTime", and
+ it's expressed in seconds, (i.e. "suggestedTime = 10")
+ '''
+
+ suggestedTime = 5 #: Suggested recheck time if publication is unfinished in seconds
+
+ def initialize(self):
+ '''
+ This method will be invoked by default __init__ of base class, so it gives
+ us the oportunity to initialize whataver we need here.
+
+ In our case, we setup a few attributes..
+ '''
+
+ # We do not check anything at marshal method, so we ensure that
+ # default values are correctly handled by marshal.
+ self._name = 'test'
+ self._reason = '' # No error, no reason for it
+ self._number = 1
+
+ def marshal(self):
+ '''
+ returns data from an instance of Sample Publication serialized
+ '''
+ return '\t'.join( [self._name, self._reason, str(self._number)] )
+
+ def unmarshal(self, data):
+ '''
+ deserializes the data and loads it inside instance.
+ '''
+ logger.debug('Data: {0}'.format(data))
+ vals = data.split('\t')
+ logger.debug('Values: {0}'.format(vals))
+ self._name = vals[0]
+ self._reason = vals[1]
+ self._number = int(vals[2])
+
+
+ def publish(self):
+ '''
+ This method is invoked whenever the administrator requests a new publication.
+
+ The method is not invoked directly (i mean, that the administration request
+ do no makes a call to this method), but a DelayedTask is saved witch will
+ initiate all publication stuff (and, of course, call this method).
+
+ You MUST implement it, so the publication do really something.
+ All publications can be synchronous or asynchronous.
+
+ The main difference between both is that first do whatever needed, (the
+ action must be fast enough to do not block core), returning State.FINISHED.
+
+ The second (asynchronous) are publications that could block the core, so
+ it have to be done in more than one step.
+
+ An example publication could be a copy of a virtual machine, where:
+ * First we invoke the copy operation to virtualization provider
+ * Second, we kept needed values inside instance so we can serialize
+ them whenever requested
+ * Returns an State.RUNNING, indicating the core that the publication
+ has started but has to finish sometime later. (We do no check
+ again the state and keep waiting here, because we will block the
+ core untill this operation is finished).
+
+ In our example wi will simple assign a name, and set number to 5. We
+ will use this number later, to make a "delay" at check if the publication
+ has finished. (see method checkState)
+
+ We also will make this publication an "stepped one", that is, it will not
+ finish at publish call but a later checkState call
+
+ Take care with instantiating threads from here. Whenever a publish returns
+ "State.RUNNING", the core will recheck it later, but not using this instance
+ and maybe that even do not use this server.
+
+ If you want to use threadings or somethin likt it, use DelayedTasks and
+ do not block it. You also musht provide the mechanism to allow those
+ DelayedTask to communicate with the publication.
+
+ One sample could be, for example, to copy a bunch of files, but we know
+ that this copy can take a long time and don't want it to take make it
+ all here, but in a separate task. Now, do you remember that "environment"
+ that is unique for every instance?, well, we can create a delayed task,
+ and pass that environment (owned by this intance) as a mechanism for
+ informing when the task is finished. (We insert at delayed tasks queue
+ an instance, not a class itself, so we can instantiate a class and
+ store it at delayed task queue.
+
+ Also note that, in that case, this class can also acomplish that by simply
+ using the suggestedTime attribute and the checkState method in most cases.
+ '''
+ self._number = 5
+ self._reason = ''
+ return State.RUNNING
+
+ def checkState(self):
+ '''
+ Our publish method will initiate publication, but will not finish it.
+ So in our sample, wi will only check if _number reaches 0, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One publish returns State.RUNNING, this task will get called untill
+ checkState returns State.FINISHED.
+
+ Also, wi will make the publication fail one of every 10 calls to this
+ method.
+
+ Note: Destroying an publication also makes use of this method, so you
+ must keep the info of that you are checking (publishing or destroying...)
+ In our case, destroy is 1-step action so this will no get called while
+ destroying...
+ '''
+ import random
+ self._number -= 1
+ # Serialization will take care of storing self._number
+
+ # One of every 10 calls
+ if random.randint(0, 9) == 9:
+ self._reason = _('Random integer was 9!!! :-)')
+ return State.ERROR
+
+ if self._number <= 0:
+ return State.FINISHED
+ else:
+ return State.RUNNING
+
+
+ def finish(self):
+ '''
+ Invoked when Publication manager noticed that the publication has finished.
+ This give us the oportunity of cleaning up things (as stored vars, etc..),
+ or initialize variables that will be needed in a later phase (by deployed
+ services)
+
+ Returned value, if any, is ignored
+ '''
+ import string
+ import random
+ # Make simply a random string
+ self._name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
+
+ def reasonOfError(self):
+ '''
+ If a publication produces an error, here we must notify the reason why
+ it happened. This will be called just after publish or checkState
+ if they return State.ERROR
+
+ Returns an string, in our case, set at checkState
+ '''
+ return self._reason
+
+ def destroy(self):
+ '''
+ This is called once a publication is no more needed.
+
+ This method do whatever needed to clean up things, such as
+ removing created "external" data (environment gets cleaned by core),
+ etc..
+
+ The retunred value is the same as when publishing, State.RUNNING,
+ State.FINISHED or State.ERROR.
+ '''
+ self._name = ''
+ self._reason = '' # In fact, this is not needed, but cleaning up things... :-)
+
+ # We do not do anything else to destroy this instance of publication
+ return State.FINISHED
+
+
+ def cancel(self):
+ '''
+ Invoked for canceling the current operation.
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+
+ Also, take into account that cancel is the initiation of, maybe, a
+ multiple-step action, so it returns, as publish and destroy does.
+
+ In our case, cancel simply invokes "destroy", that cleans up
+ things and returns that the action has finished in 1 step.
+ '''
+ return self.destroy()
+
+ # Here ends the publication needed methods.
+ # Methods provided below are specific for this publication
+ # and will be used by user deployments that uses this kind of publication
+
+ def getBaseName(self):
+ '''
+ This sample method (just for this sample publication), provides
+ the name generater for this publication. This is just a sample, and
+ this will do the work
+ '''
+ return self._name
diff --git a/trunk/server/documentation/_downloads/samples/services/SampleService.py b/trunk/server/documentation/_downloads/samples/services/SampleService.py
new file mode 100644
index 00000000..e38c96f6
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/SampleService.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as translatable, ugettext as _
+from uds.core.services import Service
+from SamplePublication import SamplePublication
+from SampleUserDeploymentOne import SampleUserDeploymentOne
+from SampleUserDeploymentTwo import SampleUserDeploymentTwo
+
+from uds.core.ui import gui
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class ServiceOne(Service):
+ '''
+ Basic service, the first part (variables) include the description of the service.
+
+ Remember to fill all variables needed, but at least you must define:
+ * typeName
+ * typeType
+ * typeDescription
+ * iconFile (defaults to service.png)
+ * publicationType, type of publication in case it needs publication.
+ If this is not provided, core will assume that the service do not
+ needs publishing.
+ * deployedType, type of deployed user service. Do not forget this!!!
+
+ The rest of them can be ommited, but its recommended that you fill all
+ declarations shown in this sample (that in fact, are all)
+
+ This description informs the core what this service really provides,
+ and how this is done. Look at description of class variables for more
+ information.
+
+ '''
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ typeName = translatable('Sample Service One')
+ #: Type used internally to identify this provider
+ typeType = 'SampleService1'
+ #: Description shown at administration interface for this provider
+ typeDescription = translatable('Sample (and dummy) service ONE')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ iconFile = 'service.png'
+
+ # Functional related data
+
+ #: If the service provides more than 1 "deployed user" (-1 = no limit,
+ #: 0 = ???? (do not use it!!!), N = max number to deploy
+ maxDeployed = -1
+ #: If we need to generate "cache" for this service, so users can access the
+ #: provided services faster. Is usesCache is True, you will need also
+ #: set publicationType, do take care about that!
+ usesCache = False
+ #: Tooltip shown to user when this item is pointed at admin interface, none
+ #: because we don't use it
+ cacheTooltip = translatable('None')
+ #: If we need to generate a "Level 2" cache for this service (i.e., L1
+ #: could be running machines and L2 suspended machines)
+ usesCache_L2 = False
+ #: Tooltip shown to user when this item is pointed at admin interface, None
+ #: also because we don't use it
+ cacheTooltip_L2 = translatable('None')
+
+ #: If the service needs a s.o. manager (managers are related to agents
+ #: provided by services itselfs, i.e. virtual machines with actors)
+ needsManager = False
+ #: If true, the system can't do an automatic assignation of a deployed user
+ #: service from this service
+ mustAssignManually = False
+
+ #: Types of publications (preparated data for deploys)
+ #: In our case, we do no need a publication, so this is None
+ publicationType = None
+ #: Types of deploys (services in cache and/or assigned to users)
+ deployedType = SampleUserDeploymentOne
+
+ # Now the form part, this service will have only two "dummy" fields
+ # If we don't indicate an order, the output order of fields will be
+ # "random"
+
+ colour = gui.ChoiceField(order = 1,
+ label = translatable('Colour'),
+ tooltip = translatable('Colour of the field'),
+ # In this case, the choice can have none value selected by default
+ required = True,
+ values = [ gui.choiceItem('red', 'Red'),
+ gui.choiceItem('green', 'Green'),
+ gui.choiceItem('blue', 'Blue'),
+ gui.choiceItem('nonsense', 'Blagenta')
+ ],
+ defvalue = '1' # Default value is the ID of the choicefield
+ )
+
+ passw = gui.PasswordField(order = 2,
+ label = translatable('Password'),
+ tooltip = translatable('Password for testing purposes'),
+ required = True,
+ defvalue = '1234' #: Default password are nonsense?? :-)
+ )
+
+ baseName = gui.TextField(order = 3,
+ label = translatable('Services names'),
+ tooltip = translatable('Base name for this user services'),
+ # In this case, the choice can have none value selected by default
+ required = True,
+ defvalue = '' # Default value is the ID of the choicefield
+ )
+
+ def initialize(self, values):
+ '''
+ We check here form values to see if they are valid.
+
+ Note that we check them throught FROM variables, that already has been
+ initialized by __init__ method of base class, before invoking this.
+ '''
+
+ # We don't need to check anything, bat because this is a sample, we do
+ # As in provider, we receive values only at new Service creation,
+ # so we only need to validate params if values is not None
+ if values is not None:
+ if self.colour.value == 'nonsense':
+ raise Service.ValidationException('The selected colour is invalid!!!')
+
+
+ # Services itself are non testeable right now, so we don't even have
+ # to provide one!!!
+
+
+ # Congratulations!!!, the needed part of your first simple service is done!
+ # Now you can go to administration panel, and check it
+ #
+ # From now onwards, we implement our own methods, that will be used by,
+ # for example, services derived from this provider
+
+ def getColour(self):
+ '''
+ Simply returns colour, for deployed user services.
+
+ Remember that choiceField.value returns the id part of the ChoiceItem
+ '''
+ return self.colour.value
+
+ def getPassw(self):
+ '''
+ Simply returns passwd, for deloyed user services
+ '''
+ return self.passw.value
+
+ def getBaseName(self):
+ '''
+ '''
+ return self.baseName.value
+
+
+
+class ServiceTwo(Service):
+ '''
+ Just a second service, no comments here (almost same that ServiceOne
+ '''
+ typeName = translatable('Sample Service Two')
+ typeType = 'SampleService2'
+ typeDescription = translatable('Sample (and dummy) service ONE+ONE')
+ iconFile = 'provider.png' #: We reuse provider icon here :-)
+
+ # Functional related data
+ maxDeployed = 5
+ usesCache = True
+ cacheTooltip = translatable('L1 cache for dummy elements')
+ usesCache_L2 = True
+ cacheTooltip_L2 = translatable('L2 cache for dummy elements')
+
+ needsManager = False
+ mustAssignManually = False
+
+ #: Types of publications. In this case, we will include a publication
+ #: type for this one
+ #: Note that this is a MUST if you indicate that needPublication
+ publicationType = SamplePublication
+ #: Types of deploys (services in cache and/or assigned to users)
+ deployedType = SampleUserDeploymentTwo
+
+
+ # Gui, we will use here the EditableList field
+ names = gui.EditableList(label=translatable('List of names'))
+
+ def __init__(self, environment, parent, values = None):
+ '''
+ We here can get a HUGE list from client.
+ Right now, this is treated same as other fields, in a near
+ future we will se how to handle this better
+ '''
+ super(ServiceTwo, self).__init__(environment, parent, values)
+
+ # No checks here
+
+ def getNames(self):
+ '''
+ For using at deployed services, really nothing
+ '''
+ return self.names.value
diff --git a/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentOne.py b/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentOne.py
new file mode 100644
index 00000000..e49d4a15
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentOne.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.services import UserDeployment
+from uds.core.util.State import State
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleUserDeploymentOne(UserDeployment):
+ '''
+ This class generates the user consumable elements of the service tree.
+
+ After creating at administration interface an Deployed Service, UDS will
+ create consumable services for users using UserDeployment class as
+ provider of this elements.
+
+
+ At class instantiation, this will receive an environment with"generator",
+ that are classes that provides a way to generate unique items.
+
+ The generators provided right now are 'mac' and 'name'. To get more info
+ about this, look at py:class:`uds.core.util.UniqueMacGenerator.UniqueNameGenerator`
+ and py:class:`uds.core.util.UniqueNameGenerator.UniqueNameGenerator`
+
+ This first sample do not uses cache. To see one with cache, see
+ SampleUserDeploymentTwo. The main difference are the "...Cache".." methods,
+ that here are not needed.
+
+ As sample also of environment storage usage, wi will use here the provider
+ storage to keep all our needed info, leaving marshal and unmarshal (needed
+ by Serializble classes, like this) empty (that is, returns '' first and does
+ nothing the second one)
+
+ Also Remember, if you don't include this class as the deployedType of the
+ SampleServiceOne, or whenever you trie to access a service of SampleServiceOne,
+ you will get an excetion that says that you havent included the deployedType.
+ '''
+
+ #: Recheck every five seconds by default (for task methods)
+ suggestedTime = 5
+
+ # Serializable needed methods
+ def marshal(self):
+ '''
+ Does nothing right here, we will use envoronment storage in this sample
+ '''
+ return ''
+
+ def unmarshal(self, str_):
+ '''
+ Does nothing here also, all data are keeped at environment storage
+ '''
+ pass
+
+
+ def getName(self):
+ '''
+ We override this to return a name to display. Default inplementation
+ (in base class), returns getUniqueIde() value
+ This name will help user to identify elements, and is only used
+ at administration interface.
+
+ We will use here the environment name provided generator to generate
+ a name for this element.
+
+ The namaGenerator need two params, the base name and a length for a
+ numeric incremental part for generating unique names. This are unique for
+ all UDS names generations, that is, UDS will not generate this name again
+ until this name is freed, or object is removed, what makes its environment
+ to also get removed, that makes all uniques ids (names and macs right now)
+ to also get released.
+
+ Every time get method of a generator gets called, the generator creates
+ a new unique name, so we keep the first generated name cached and don't
+ generate more names. (Generator are simple utility classes)
+ '''
+ name = self.storage().readData('name')
+ if name is None:
+ name = self.nameGenerator().get( self.service().getBaseName()
+ + '-' + self.service().getColour(), 3 )
+ # Store value for persistence
+ self.storage().saveData('name', name)
+
+ return name
+
+ def setIp(self, ip):
+ '''
+ In our case, there is no OS manager associated with this, so this method
+ will never get called, but we put here as sample.
+
+ Whenever an os manager actor notifies the broker the state of the service
+ (mainly machines), the implementation of that os manager can (an probably will)
+ need to notify the IP of the deployed service. Remember that UDS treats with
+ IP services, so will probable needed in every service that you will create.
+ :note: This IP is the IP of the "consumed service", so the transport can
+ access it.
+ '''
+ self.storage().saveData('ip', str(ip))
+
+ def getUniqueId(self):
+ '''
+ Return and unique identifier for this service.
+ In our case, we will generate a mac name, that can be also as sample
+ of 'mac' generator use, and probably will get used something like this
+ at some services.
+
+ The get method of a mac generator takes one param, that is the mac range
+ to use to get an unused mac.
+ '''
+ mac = self.storage().readData('mac')
+ if mac is None:
+ mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
+ self.storage().saveData('mac', mac)
+ return mac
+
+ def getIp(self):
+ '''
+ We need to implement this method, so we can return the IP for transports
+ use. If no IP is known for this service, this must return None
+
+ If our sample do not returns an IP, IP transport will never work with
+ this service. Remember in real cases to return a valid IP address if
+ the service is accesible and you alredy know that (for example, because
+ the IP has been assigend via setIp by an os manager) or because
+ you get it for some other method.
+
+ Storage returns None if key is not stored.
+
+ :note: Keeping the IP address is responsibility of the User Deployment.
+ Every time the core needs to provide the service to the user, or
+ show the IP to the administrator, this method will get called
+
+ '''
+ ip = self.storage().readData('ip')
+ if ip is None:
+ ip = '192.168.0.34' # Sample IP for testing purposses only
+ return ip
+
+ def setReady(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+
+ This method exist for this kind of situations (i will explain it with a
+ sample)
+
+ Imagine a Service tree (Provider, Service, ...) for virtual machines.
+ This machines will get created by the UserDeployment implementation, but,
+ at some time, the machine can be put at in an state (suspend, shut down)
+ that will make the transport impossible to connect with it.
+
+ This method, in this case, will check the state of the machine, and if
+ it is "ready", that is, powered on and accesible, it will return
+ "State.FINISHED". If the machine is not accesible (has ben erased, for
+ example), it will return "State.ERROR" and store a reason of error so UDS
+ can ask for it and present this information to the Administrator.
+
+ If the machine powered off, or suspended, or any other state that is not
+ directly usable but can be put in an usable state, it will return
+ "State.RUNNING", and core will use checkState to see when the operation
+ has finished.
+
+ I hope this sample is enough to explain the use of this method..
+ '''
+
+ # In our case, the service is always ready
+ return State.FINISHED
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The user parameter is not realy neded, but provided. It indicates the
+ Database User Object (see py:mod:`uds.modules`) to which this deployed
+ user service will be assigned to.
+
+ This method will get called whenever a new deployed service for an user
+ is needed. This will give this class the oportunity to create
+ a service that is assigned to an user.
+
+ The way of using this method is as follows:
+
+ If the service gets created in "one step", that is, before the return
+ of this method, the consumable service for the user gets created, it
+ will return "State.FINISH".
+ If the service needs more steps (as in this case), we will return
+ "State.RUNNING", and if it has an error, it wil return "State.ERROR" and
+ store an error string so administration interface can show it.
+
+ We do not use user for anything, as in most cases will be.
+ '''
+ import random
+
+ self.storage().saveData('count', '0')
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self.storage().saveData('error', 'Random error at deployForUser :-)')
+ return State.ERROR
+
+ return State.RUNNING
+
+
+ def checkState(self):
+ '''
+ Our deployForUser method will initiate the consumable service deployment,
+ but will not finish it.
+
+ So in our sample, we will only check if a number reaches 5, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One deployForUser returns State.RUNNING, this task will get called until
+ checkState returns State.FINISHED.
+
+ Also, we will make the publication fail one of every 10 calls to this
+ method.
+
+ Note: Destroying, canceling and deploying for cache also makes use of
+ this method, so you must keep the info of that you are checking if you
+ need it.
+ In our case, destroy is 1-step action so this will no get called while
+ destroying, and cancel will simply invoke destroy
+ '''
+ import random
+
+ count = int(self.storage().readData('count')) + 1
+ # Count is always a valid value, because this method will never get
+ # called before deployForUser, deployForCache, destroy or cancel.
+ # In our sample, we only use checkState in case of deployForUser,
+ # so at first call count will be 0.
+ if count >= 5:
+ return State.FINISHED
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self.storage().saveData('error', 'Random error at checkState :-)')
+ return State.ERROR
+
+ self.storage().saveData('count', str(count))
+ return State.RUNNING
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter wether it is for cache or for an user)
+
+ This gives the oportunity to make something at that moment.
+ :note: You can also make these operations at checkState, this is really
+ not needed, but can be provided (default implementation of base class does
+ nothing)
+ '''
+ # Note that this is not really needed, is just a sample of storage use
+ self.storage().remove('count')
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This gives the User Deployment an oportunity to do whatever actions
+ are required so the service puts at a correct state for using by a service.
+
+ In our sample, the service is always ready, so this does nothing.
+
+ This is not a task method. All level 1 cache items can be diretly
+ assigned to an user with no more work needed, but, if something is needed,
+ here you can do whatever you need
+ '''
+ pass
+
+ def userLoggedIn(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We store the value at storage, but never get used, just an example
+ self.storage().saveData('user', user)
+
+ def userLoggedOut(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We do nothing more that remove the user
+ self.storage().remove('user')
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+ '''
+ return self.storage().readData('error') or 'No error'
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Invoked for destroying a deployed service
+ Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
+ @return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState)
+ '''
+ return State.FINISHED
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+ '''
+ return State.FINISHED
+
\ No newline at end of file
diff --git a/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentTwo.py b/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentTwo.py
new file mode 100644
index 00000000..687971e7
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/SampleUserDeploymentTwo.py
@@ -0,0 +1,469 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.services import UserDeployment
+from uds.core.util.State import State
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleUserDeploymentTwo(UserDeployment):
+ '''
+ This class generates the user consumable elements of the service tree.
+
+ This is almost the same as SampleUserDeploymentOne, but differs that this one
+ uses the publication to get data from it, in a very basic way.
+
+ After creating at administration interface an Deployed Service, UDS will
+ create consumable services for users using UserDeployment class as
+ provider of this elements.
+
+ At class instantiation, this will receive an environment with"generator",
+ that are classes that provides a way to generate unique items.
+
+ The generators provided right now are 'mac' and 'name'. To get more info
+ about this, look at py:class:`uds.core.util.UniqueMacGenerator.UniqueNameGenerator`
+ and py:class:`uds.core.util.UniqueNameGenerator.UniqueNameGenerator`
+
+ As sample also of environment storage usage, wi will use here the provider
+ storage to keep all our needed info, leaving marshal and unmarshal (needed
+ by Serializable classes, like this) empty (that is, returns '' first and does
+ nothing the second one)
+
+ Also Remember, if you don't include this class as the deployedType of the
+ SampleServiceTwo, or whenever you try to access a service of SampleServiceTwo,
+ you will get an exception that says that you haven't included the deployedType.
+ '''
+
+ #: Recheck every five seconds by default (for task methods)
+ suggestedTime = 2
+
+ def initialize(self):
+ '''
+ Initialize default attributes values here. We can do whatever we like,
+ but for this sample this is just right...
+ '''
+ self._name = ''
+ self._ip = ''
+ self._mac = ''
+ self._error = ''
+ self._count = 0
+
+ # Serializable needed methods
+ def marshal(self):
+ '''
+ Marshal own data, in this sample we will marshal internal needed
+ attributes.
+
+ In this case, the data will be store with the database record. To
+ minimize database storage usage, we will "zip" data before returning it.
+ Anyway, we should keep this data as low as possible, we also have an
+ storage for loading larger data.
+
+ :note: It's a good idea when providing marshalers, to store a 'version'
+ beside the values, so we can, at a later stage, treat with old
+ data for current modules.
+ '''
+ data = '\t'.join(['v1', self._name, self._ip, self._mac, self._error,
+ str(self._count)])
+ return data.encode('zip')
+
+ def unmarshal(self, str_):
+ '''
+ We unmarshal the content.
+ '''
+ data = str_.decode('zip').split('\t')
+ # Data Version check
+ # If we include some new data at some point in a future, we can
+ # add "default" values at v1 check, and load new values at 'v2' check.
+ if data[0] == 'v1':
+ self._name, self._ip, self._mac, self._error, count = data[1:]
+ self._count = int(count)
+
+ def getName(self):
+ '''
+ We override this to return a name to display. Default implementation
+ (in base class), returns getUniqueIde() value
+ This name will help user to identify elements, and is only used
+ at administration interface.
+
+ We will use here the environment name provided generator to generate
+ a name for this element.
+
+ The namaGenerator need two params, the base name and a length for a
+ numeric incremental part for generating unique names. This are unique for
+ all UDS names generations, that is, UDS will not generate this name again
+ until this name is freed, or object is removed, what makes its environment
+ to also get removed, that makes all unique ids (names and macs right now)
+ to also get released.
+
+ Every time get method of a generator gets called, the generator creates
+ a new unique name, so we keep the first generated name cached and don't
+ generate more names. (Generator are simple utility classes)
+ '''
+ if self._name == '':
+ self._name = self.nameGenerator().get( self.publication().getBaseName(),
+ 3 )
+ # self._name will be stored when object is marshaled
+ return self._name
+
+ def setIp(self, ip):
+ '''
+ In our case, there is no OS manager associated with this, so this method
+ will never get called, but we put here as sample.
+
+ Whenever an os manager actor notifies the broker the state of the service
+ (mainly machines), the implementation of that os manager can (an probably will)
+ need to notify the IP of the deployed service. Remember that UDS treats with
+ IP services, so will probable needed in every service that you will create.
+ :note: This IP is the IP of the "consumed service", so the transport can
+ access it.
+ '''
+ self._ip = ip
+
+ def getUniqueId(self):
+ '''
+ Return and unique identifier for this service.
+ In our case, we will generate a mac name, that can be also as sample
+ of 'mac' generator use, and probably will get used something like this
+ at some services.
+
+ The get method of a mac generator takes one param, that is the mac range
+ to use to get an unused mac.
+
+ The mac generated is not used by anyone, it will not depend on
+ the range, the generator will take care that this mac is unique
+ and in the range provided, or it will return None. The ranges
+ are wide enough to ensure that we always will get a mac address
+ in this case, but if this is not your case, take into account that
+ None is a possible return value, and in that case, you should return an
+ invalid id right now. Every time a task method is invoked, the core
+ will try to update the value of the unique id using this method, so
+ that id can change with time. (In fact, it's not unique at database level,
+ it's unique in the sense that you must return an unique id that can, for
+ example, be used by os managers to identify this element).
+
+ :note: Normally, getting out of macs in the mac pool is a bad thing... :-)
+ '''
+ if self._mac == '':
+ self._mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
+ return self._mac
+
+ def getIp(self):
+ '''
+ We need to implement this method, so we can return the IP for transports
+ use. If no IP is known for this service, this must return None
+
+ If our sample do not returns an IP, IP transport will never work with
+ this service. Remember in real cases to return a valid IP address if
+ the service is accesible and you alredy know that (for example, because
+ the IP has been assigend via setIp by an os manager) or because
+ you get it for some other method.
+
+ Storage returns None if key is not stored.
+
+ :note: Keeping the IP address is responsibility of the User Deployment.
+ Every time the core needs to provide the service to the user, or
+ show the IP to the administrator, this method will get called
+
+ '''
+ if self._ip == '':
+ return '192.168.0.34' # Sample IP for testing purposes only
+ return self._ip
+
+ def setReady(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+
+ This method exist for this kind of situations (i will explain it with a
+ sample)
+
+ Imagine a Service tree (Provider, Service, ...) for virtual machines.
+ This machines will get created by the UserDeployment implementation, but,
+ at some time, the machine can be put at in an state (suspend, shut down)
+ that will make the transport impossible to connect with it.
+
+ This method, in this case, will check the state of the machine, and if
+ it is "ready", that is, powered on and accessible, it will return
+ "State.FINISHED". If the machine is not accessible (has been erased, for
+ example), it will return "State.ERROR" and store a reason of error so UDS
+ can ask for it and present this information to the Administrator.
+
+ If the machine powered off, or suspended, or any other state that is not
+ directly usable but can be put in an usable state, it will return
+ "State.RUNNING", and core will use checkState to see when the operation
+ has finished.
+
+ I hope this sample is enough to explain the use of this method..
+ '''
+
+ # In our case, the service is always ready
+ return State.FINISHED
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The user parameter is not realy neded, but provided. It indicates the
+ Database User Object (see py:mod:`uds.modules`) to which this deployed
+ user service will be assigned to.
+
+ This method will get called whenever a new deployed service for an user
+ is needed. This will give this class the oportunity to create
+ a service that is assigned to an user.
+
+ The way of using this method is as follows:
+
+ If the service gets created in "one step", that is, before the return
+ of this method, the consumable service for the user gets created, it
+ will return "State.FINISH".
+ If the service needs more steps (as in this case), we will return
+ "State.RUNNING", and if it has an error, it wil return "State.ERROR" and
+ store an error string so administration interface can show it.
+
+ We do not use user for anything, as in most cases will be.
+ '''
+ import random
+
+ self._count = 0
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ # Note that we can mark this string as translatable, and return
+ # it translated at reasonOfError method
+ self._error = 'Random error at deployForUser :-)'
+ return State.ERROR
+
+ return State.RUNNING
+
+ def deployForCache(self, cacheLevel):
+ '''
+ Deploys a user deployment as cache.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ In our sample, this will do exactly the same as deploy for user,
+ except that it will never will give an error.
+
+ See deployForUser for a description of what this method should do.
+
+ :note: deployForCache is invoked whenever a new cache element is needed
+ for an specific user deployment. It will also indicate for what
+ cache level (L1, L2) is the deployment
+ '''
+ self._count = 0
+ return State.RUNNING
+
+ def moveToCache(self, newLevel):
+ '''
+ This method is invoked whenever the core needs to move from the current
+ cache level to a new cache level an user deployment.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ We only provide newLevel, because there is only two cache levels, so if
+ newLevel is L1, the actual is L2, and if it is L2, the actual is L1.
+
+ Actually there is no possibility to move assigned services again back to
+ cache. If some service needs that kind of functionallity, this must be
+ provided at service level (for example, when doing publishing creating
+ a number of services that will be used, released and reused by users).
+
+ Also, user deployments that are at cache level 2 will never get directly
+ assigned to user. First, it will pass to L1 and then it will get assigned.
+
+ A good sample of a real implementation of this is moving a virtual machine
+ from a "suspended" state to "running" state to assign it to an user.
+
+ In this sample, there is L2 cache also, but moving from L1 to L2 and
+ from L2 to L1 is doing really nothing, so this method will do nothing.
+
+ In a real scenario, we will, for example, suspend or resume virtual machine
+ and, return State.RUNNING and at checkState check if this task is completed.
+ '''
+ pass
+
+ def checkState(self):
+ '''
+ Our deployForUser method will initiate the consumable service deployment,
+ but will not finish it.
+
+ So in our sample, we will only check if a number reaches 5, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One deployForUser returns State.RUNNING, this task will get called until
+ checkState returns State.FINISHED.
+
+ Also, we will make the user deployment fail one of every 10 calls to this
+ method.
+
+ Note: Destroying, canceling and deploying for cache also makes use of
+ this method, so you must keep the info of that you are checking if you
+ need it.
+
+ In our case, destroy is 1-step action so this will no get called while
+ destroying, and cancel will simply invoke destroy. Cache deployment is
+ exactly as user deployment, except that the core will not assign it to
+ anyone, and cache moving operations is
+ '''
+ import random
+
+ self._count += 1
+ # Count is always a valid value, because this method will never get
+ # called before deployForUser, deployForCache, destroy or cancel.
+ # In our sample, we only use checkState in case of deployForUser,
+ # so at first call count will be 0.
+ if self._count >= 5:
+ return State.FINISHED
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self._error = 'Random error at checkState :-)'
+ return State.ERROR
+
+ return State.RUNNING
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter whether it is for cache or for an user)
+
+ This gives the opportunity to make something at that moment.
+
+ :note: You can also make these operations at checkState, this is really
+ not needed, but can be provided (default implementation of base class does
+ nothing)
+ '''
+ # We set count to 0, not needed but for sample purposes
+ self._count = 0
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This is not a task method right now, simply a notification. This means
+ that L1 cache items must be directly usable (except for the readyness part)
+ by users in a single step operation.
+
+ Note that there will be an setReady call before letting the user consume
+ this user deployment, so this is more informational (so, if you keep at
+ what cache level is this instance, you can update it) than anything else.
+
+ This is not a task method. All level 1 cache items can be dircetly
+ assigned to an user with no more work needed, but, if something is needed,
+ here you can do whatever you need.
+
+ user is a Database user object.
+ '''
+ logger.debug('Assigned to user {0}'.format(user))
+
+ def userLoggedIn(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responsibility of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actors.
+ '''
+ # We store the value at storage, but never get used, just an example
+ self.storage().saveData('user', user)
+
+ def userLoggedOut(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We do nothing more that remove the user
+ self.storage().remove('user')
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+
+ :note: Remember that you can use ugettext to translate this error to
+ user language whenever it is possible. (This one will get invoked
+ directly from admin interface and, as so, will have translation
+ environment correctly set up.
+ '''
+ return self._error
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Invoked for destroying a deployed service
+ Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
+ @return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState)
+ '''
+ return State.FINISHED
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+ '''
+ return State.FINISHED
diff --git a/trunk/server/documentation/_downloads/samples/services/__init__.py b/trunk/server/documentation/_downloads/samples/services/__init__.py
new file mode 100644
index 00000000..38a9126a
--- /dev/null
+++ b/trunk/server/documentation/_downloads/samples/services/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Sample Service module.
+
+This package simply shows how a new service can be implemented.
+
+
+The first thing to do in every package that is a module is register the
+class that is responsible of providing the module with the system.
+
+For this, we must simply import the class at __init__, UDS will take care
+of the rest
+'''
+
+from SampleProvider import Provider
+
diff --git a/trunk/server/documentation/_downloads/samples/services/provider.png b/trunk/server/documentation/_downloads/samples/services/provider.png
new file mode 100644
index 00000000..d2a954d4
Binary files /dev/null and b/trunk/server/documentation/_downloads/samples/services/provider.png differ
diff --git a/trunk/server/documentation/_downloads/samples/services/service.png b/trunk/server/documentation/_downloads/samples/services/service.png
new file mode 100644
index 00000000..c7b626c4
Binary files /dev/null and b/trunk/server/documentation/_downloads/samples/services/service.png differ
diff --git a/trunk/server/documentation/_images/LogoUDS.png b/trunk/server/documentation/_images/LogoUDS.png
new file mode 100644
index 00000000..f3196430
Binary files /dev/null and b/trunk/server/documentation/_images/LogoUDS.png differ
diff --git a/trunk/server/documentation/api/index.rst b/trunk/server/documentation/api/index.rst
new file mode 100644
index 00000000..1353be23
--- /dev/null
+++ b/trunk/server/documentation/api/index.rst
@@ -0,0 +1,9 @@
+==============
+UDS's core API
+==============
+
+.. toctree::
+
+ models
+ modules
+ /development/samples/samples
diff --git a/trunk/server/documentation/api/models.rst b/trunk/server/documentation/api/models.rst
new file mode 100644
index 00000000..e2639d1a
--- /dev/null
+++ b/trunk/server/documentation/api/models.rst
@@ -0,0 +1,24 @@
+===================
+UDS Database Models
+===================
+
+This section describes de models used in UDS.
+
+The models described here are implemented using Django models, so you can get more
+info about Django models functionalty at `Django project website `_
+
+The function of the models inside UDS is to provide the persistence needed by
+the core and by other utility classes that are provided, such as a Cache, Storage
+or unique IDs.
+
+Right now the models are used all over UDS, but with time we will limit the use
+of this models to be done through managers or utility clases designed for that
+purpose.
+
+.. toctree::
+
+ models/services
+ models/authentication
+ models/transport
+ models/other
+
diff --git a/trunk/server/documentation/api/models/authentication.rst b/trunk/server/documentation/api/models/authentication.rst
new file mode 100644
index 00000000..75d64235
--- /dev/null
+++ b/trunk/server/documentation/api/models/authentication.rst
@@ -0,0 +1,25 @@
+=============================
+Authentication Related models
+=============================
+.. toctree::
+ :maxdepth: 2
+
+.. module:: uds.models
+
+.. autoclass:: Authenticator
+ :members:
+ :show-inheritance:
+
+.. autoclass:: User
+ :members:
+ :show-inheritance:
+
+.. autoclass:: Group
+ :members:
+ :show-inheritance:
+
+.. autoclass:: UserPreference
+ :members:
+ :show-inheritance:
+
+
diff --git a/trunk/server/documentation/api/models/other.rst b/trunk/server/documentation/api/models/other.rst
new file mode 100644
index 00000000..6c6f9e2e
--- /dev/null
+++ b/trunk/server/documentation/api/models/other.rst
@@ -0,0 +1,41 @@
+============
+Other models
+============
+
+Environment related
+-------------------
+
+.. module:: uds.models
+
+.. toctree::
+ :maxdepth: 2
+
+.. autoclass:: Cache
+ :members:
+ :show-inheritance:
+
+.. autoclass:: Storage
+ :members:
+ :show-inheritance:
+
+.. autoclass:: UniqueId
+ :members:
+ :show-inheritance:
+
+Module related
+--------------
+
+.. autoclass:: Config
+ :members:
+ :show-inheritance:
+
+Scheduling and background workers related
+-----------------------------------------
+
+.. autoclass:: Scheduler
+ :members:
+ :show-inheritance:
+
+.. autoclass:: DelayedTask
+ :members:
+ :show-inheritance:
diff --git a/trunk/server/documentation/api/models/services.rst b/trunk/server/documentation/api/models/services.rst
new file mode 100644
index 00000000..c11794cf
--- /dev/null
+++ b/trunk/server/documentation/api/models/services.rst
@@ -0,0 +1,33 @@
+======================
+Service Related models
+======================
+
+This models takes cares of persistence of the Services and its associated elements.
+
+ DESCRIBE HIEARARCHY HERE
+
+
+.. toctree::
+ :maxdepth: 2
+
+.. module:: uds.models
+
+.. autoclass:: Provider
+ :members:
+ :show-inheritance:
+
+.. autoclass:: Service
+ :members:
+ :show-inheritance:
+
+.. autoclass:: DeployedService
+ :members:
+ :show-inheritance:
+
+.. autoclass:: DeployedServicePublication
+ :members:
+ :show-inheritance:
+
+.. autoclass:: UserService
+ :members:
+ :show-inheritance:
diff --git a/trunk/server/documentation/api/models/transport.rst b/trunk/server/documentation/api/models/transport.rst
new file mode 100644
index 00000000..3fb2545c
--- /dev/null
+++ b/trunk/server/documentation/api/models/transport.rst
@@ -0,0 +1,18 @@
+========================
+Transport Related models
+========================
+.. toctree::
+ :maxdepth: 2
+
+.. module:: uds.models
+
+.. autoclass:: Transport
+ :members:
+ :show-inheritance:
+
+.. autoclass:: Network
+ :members:
+ :show-inheritance:
+
+
+
diff --git a/trunk/server/documentation/api/modules.rst b/trunk/server/documentation/api/modules.rst
new file mode 100644
index 00000000..5aaebc0d
--- /dev/null
+++ b/trunk/server/documentation/api/modules.rst
@@ -0,0 +1,18 @@
+===========
+UDS Modules
+===========
+
+
+Modules are the basic component of plugin architecture of UDS.
+
+As so, they are spreadly covered here, and with
+:doc:`samples ` must give enough information for
+allowing anyone to develop their own modules.
+
+.. toctree::
+
+ modules/BaseModule
+ modules/FormFields
+ modules/ServiceModules
+ modules/AuthenticatorModule
+
diff --git a/trunk/server/documentation/api/modules/AuthenticatorModule.rst b/trunk/server/documentation/api/modules/AuthenticatorModule.rst
new file mode 100644
index 00000000..b210793a
--- /dev/null
+++ b/trunk/server/documentation/api/modules/AuthenticatorModule.rst
@@ -0,0 +1,27 @@
+=====================
+Authenticator Modules
+=====================
+
+Authenticator modules are responsible of providing the user authentication
+part inside UDS.
+
+They are composed of a package where it is provided and, at least, the following
+elements:
+
+ * One icon for administration interface representation. Icon is png file of
+ 16x16.
+ * One class, derived from uds.core.auths.Authenticator, providing the needed
+ logic for that authenticator.
+ * Registration of the class inside uds at package's __init__.
+
+All packages included inside uds.auths will automatically be imported, but
+the authenticators needs to register as valid authenticators, and the best place
+to do that is at the authenticator's package __init__.
+
+The best way to understand what you need to create your own authenticator,
+is to look at :doc:`modules samples `
+
+
+.. toctree::
+
+ auths/Authenticator
\ No newline at end of file
diff --git a/trunk/server/documentation/api/modules/BaseModule.rst b/trunk/server/documentation/api/modules/BaseModule.rst
new file mode 100644
index 00000000..294d2add
--- /dev/null
+++ b/trunk/server/documentation/api/modules/BaseModule.rst
@@ -0,0 +1,53 @@
+===========
+Base Module
+===========
+
+The Base module is the base class used for all modules of UDS.
+
+In order to deveplop an UDS Module, there is a number of basic methods that you must provide.
+
+There are the clases that are base of BaseModule, that are:
+ * BaseModule_
+ * Environmentable_
+ * Serializable_
+ * UserInterface_
+
+.. toctree::
+
+BaseModule
+----------
+
+.. module:: uds.core
+
+.. autoclass:: Module
+ :members:
+
+Environmentable
+---------------
+
+.. autoclass:: Environmentable
+ :members:
+
+
+Serializable
+------------
+
+.. autoclass:: Serializable
+ :members:
+
+
+UserInterface
+-------------
+
+ UserInterface is the class responsible for managing the Field Descriptions of modules.
+
+ This fields descriptions are intended for allowing an easy exposition of configuration form via the
+ administration interface.
+
+ You can obtain more information about user interface fields at :doc:`User interface fields types `.
+
+.. module:: uds.core.ui.UserInterface
+
+.. autoclass:: UserInterface
+ :members:
+
diff --git a/trunk/server/documentation/api/modules/FormFields.rst b/trunk/server/documentation/api/modules/FormFields.rst
new file mode 100644
index 00000000..3b15da2a
--- /dev/null
+++ b/trunk/server/documentation/api/modules/FormFields.rst
@@ -0,0 +1,33 @@
+Form Fields
+===========
+
+Form Fields are utility clases provided for allowing easy communication of modules
+and administration interface.
+
+It helps to define the administration level forms that will be used to manage
+different modules (service providers, services, authenticators, transports, ...)
+
+All modules that needs to be presented to admin users, use UserInterface as one
+of their base class.
+
+Think that not all interfaces needed by different modules need a direct representation
+at administration interface level, (for example, UserDeployment do not need to be
+managed by administrators, nor publications, both corresponding to service modules).
+
+.. module:: uds.core.ui.UserInterface
+
+.. toctree::
+
+
+The types of fields provided are:
+ * :py:class:`gui.TextField`
+ * :py:class:`gui.NumericField`
+ * :py:class:`gui.PasswordField`
+ * :py:class:`gui.HiddenField`
+ * :py:class:`gui.CheckBoxField`
+ * :py:class:`gui.ChoiceField`
+ * :py:class:`gui.MultiChoiceField`
+ * :py:class:`gui.EditableList`
+
+.. autoclass:: gui
+ :members: InputField, TextField, NumericField, PasswordField, HiddenField, CheckBoxField, ChoiceField, MultiChoiceField, EditableList
diff --git a/trunk/server/documentation/api/modules/ServiceModules.rst b/trunk/server/documentation/api/modules/ServiceModules.rst
new file mode 100644
index 00000000..a3c9c1e4
--- /dev/null
+++ b/trunk/server/documentation/api/modules/ServiceModules.rst
@@ -0,0 +1,53 @@
+===============
+Service Modules
+===============
+
+Service modules are responsible for giving the user consumable ip services for
+users.
+
+They are composed of a package where it is provided, at least, the following
+elements:
+
+ * One icon for administration interface representation. Icon is png file of
+ 16x16.
+ * A Full tree of classes, derived from interfaces (descrived below)
+ * Registration of the class inside UDS at package's __init__.
+
+All packages included inside uds.services will automatically be imported, but
+the service providers (root of service trees) needs to register as valid
+providers, and the best place to do that is at the authenticator's package __init__.
+
+the Full tree of classes needed by the service modules are:
+
+ * **Provider**: This is the root tree of any service. It represents an agrupation
+ of services under the same root. As sample, a service provider can be an
+ Open nebula server, an VC, or whataver is a common root for a number of services.
+ * **Service**: This is the representation of what a service will give to an user.
+ As such, this is not what the user will consume, but this is more the definition
+ of what the user will consume. Before assigning a service to an user, the admin
+ will need to declare a "Deployed Service", that is a definition, using this service
+ an a number of other modules, of what the user will consume. Inside this service
+ we need to provide the information needed for deploying an user consumable item,
+ such as if it needs to be "prepared", if it supports cache, if it must be assigned
+ to an user "manually", and all the custom data that the user deployments and publications
+ will need.
+ * **Publication**. Some services, before being assigned to users, needs some kind of
+ preparation. This process of preparation is called here "publication". The service
+ itself will declare if it needs a publication and, if needed, who is responsible of
+ that. Services with needed publication will use this kind of class to provide
+ such preparation.
+ * **User Deployment**. This is what will provide the final user consumable service.
+ The user deployment is the last responsible for, using the provided service
+ and provided publication (if needed), to create the elements that the user will
+ consume.
+
+The best way to understand what you need to create your own services,
+is to look at :doc:`modules samples `
+
+.. toctree::
+
+ services/Provider
+ services/Service
+ services/Publication
+ services/UserDeployment
+ services/Exceptions
\ No newline at end of file
diff --git a/trunk/server/documentation/api/modules/auths/Authenticator.rst b/trunk/server/documentation/api/modules/auths/Authenticator.rst
new file mode 100644
index 00000000..f30d9fb7
--- /dev/null
+++ b/trunk/server/documentation/api/modules/auths/Authenticator.rst
@@ -0,0 +1,15 @@
+=======================
+Authenticator Interface
+=======================
+
+The authenticator class is in fact an interface. UDS authenticators must derive
+from this, and must provide the logic so UDS can manage the users and groups that
+an authenticator provides.
+
+
+.. toctree::
+
+.. module:: uds.core.auths
+
+.. autoclass:: Authenticator
+ :members:
diff --git a/trunk/server/documentation/api/modules/services/Exceptions.rst b/trunk/server/documentation/api/modules/services/Exceptions.rst
new file mode 100644
index 00000000..f137b05b
--- /dev/null
+++ b/trunk/server/documentation/api/modules/services/Exceptions.rst
@@ -0,0 +1,9 @@
+==================
+Service Exceptions
+==================
+
+.. toctree::
+
+.. automodule:: uds.core.services.Exceptions
+ :members:
+
diff --git a/trunk/server/documentation/api/modules/services/Provider.rst b/trunk/server/documentation/api/modules/services/Provider.rst
new file mode 100644
index 00000000..ad7fde37
--- /dev/null
+++ b/trunk/server/documentation/api/modules/services/Provider.rst
@@ -0,0 +1,27 @@
+==================
+Provider interface
+==================
+
+The provider class is the root class of the module. It keeps the common information
+needed by all services provided by this "provider".
+
+Think about a provider as the class that will declare all stuff neded by core and
+child services to provide and administrator user a way to create services to be
+consumed by users.
+
+One good example is a Virtualization server. Here we keep information about that
+server (ip address, protocol, ....) and services provided by that "provider" will
+make use of that information to make the administrator not provide it once an again
+for every service we put on that virtualization server.
+
+.. toctree::
+
+.. module:: uds.core.services
+
+For a detailed example of a service provider, you can see the provided
+:doc:`provider sample `
+
+.. autoclass:: ServiceProvider
+ :members:
+
+
diff --git a/trunk/server/documentation/api/modules/services/Publication.rst b/trunk/server/documentation/api/modules/services/Publication.rst
new file mode 100644
index 00000000..290e9150
--- /dev/null
+++ b/trunk/server/documentation/api/modules/services/Publication.rst
@@ -0,0 +1,30 @@
+=====================
+Publication interface
+=====================
+
+The publication class is in fact an interface. It represents, in those case that
+a service needs the preparation, the logic for that preparation.
+
+So the publication class is responsible of doing whatever is needed to get the
+deployed service (that is the compound of a service, an os manager, transports
+and authenticators) ready for deploying user consumables.
+
+Note that not all services needs to implement this class, only in those case
+where that service declares that a publication is needed.
+
+
+As functional sample of a publication, imagine that we want to assing KVM COW
+machines to users. The publication class can make a clone of the base machine
+(that the service itself has taken note of which one is), and then the COWs will
+be created from this cloned machine.
+
+.. toctree::
+
+.. module:: uds.core.services
+
+For a detailed example of a service provider, you can see the provided
+:doc:`publication sample `
+
+.. autoclass:: Publication
+ :members:
+
diff --git a/trunk/server/documentation/api/modules/services/Service.rst b/trunk/server/documentation/api/modules/services/Service.rst
new file mode 100644
index 00000000..6e26c128
--- /dev/null
+++ b/trunk/server/documentation/api/modules/services/Service.rst
@@ -0,0 +1,25 @@
+=================
+Service interface
+=================
+
+The service class is in fact an interface. It represents the base for all user
+deployments (that is, consumable user services) that will be provided.
+
+As such, the service is responsible for keeping the information that, at deployments,
+will be neded by provided user consumable services.
+
+A good sample of a service can be a KVM machine that will be copied COW and that COWs
+will be assigned to users. In that case, we will collect which machine will be copied,
+where it is to be copied, an a few more params that the user deployments will need.
+
+.. toctree::
+
+.. module:: uds.core.services
+
+For a detailed example of a service provider, you can see the provided
+:doc:`service sample `
+
+.. autoclass:: Service
+ :members:
+
+
diff --git a/trunk/server/documentation/api/modules/services/UserDeployment.rst b/trunk/server/documentation/api/modules/services/UserDeployment.rst
new file mode 100644
index 00000000..6f97c2de
--- /dev/null
+++ b/trunk/server/documentation/api/modules/services/UserDeployment.rst
@@ -0,0 +1,23 @@
+========================
+UserDeployment interface
+========================
+
+The user deployment class is in fact an interface. It represents the final consumable
+that will be assigned to an user, and, as such, it must provide some mechanisms to
+allow core to manage those consumables.
+
+A good sample of an user deployment can be a KVM Virtual Machine, cloned COW from
+another, and assigned to an user.
+
+.. toctree::
+
+.. module:: uds.core.services
+
+For detailed examples of a couple of user deployments, you can see the provided
+:doc:`service sample ` and
+:doc:`service sample `
+
+.. autoclass:: UserDeployment
+ :members:
+
+
diff --git a/trunk/server/documentation/conf.py b/trunk/server/documentation/conf.py
new file mode 100644
index 00000000..239d5cf8
--- /dev/null
+++ b/trunk/server/documentation/conf.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+#
+# UDS documentation build configuration file, created by
+# sphinx-quickstart on Mon Jun 18 01:41:48 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('../src/'))
+
+from server import settings
+from django.core.management import setup_environ
+setup_environ(settings)
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'UDS'
+copyright = u'2012, Virtual Cable S.L.U.'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+# default, sphinxdoc, traditional, nature, scrolls, agogo, haiku, pyramid
+html_theme = 'sphinxdoc'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {
+ # 'stickysidebar' : False,
+ # 'collapsiblesidebar' : True,
+ # 'externalrefs' : True,
+
+}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'UDSdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+'papersize': 'a4paper',
+
+# The font size ('10pt', '11pt' or '12pt').
+'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+'preamble': '\setcounter{tocdepth}{6}',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'UDS.tex', u'UDS Documentation',
+ u'Virtual Cable S.L.U.', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+latex_logo = '_images/LogoUDS.png'
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+latex_show_pagerefs = True
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'uds', u'UDS Documentation',
+ [u'Virtual Cable S.L.U.'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'UDS', u'UDS Documentation',
+ u'Virtual Cable S.L.U.', 'UDS', 'Universal Desktop Services.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/trunk/server/documentation/development/architecture.rst b/trunk/server/documentation/development/architecture.rst
new file mode 100644
index 00000000..0bf2b1fd
--- /dev/null
+++ b/trunk/server/documentation/development/architecture.rst
@@ -0,0 +1,36 @@
+==================
+UDS's architecture
+==================
+
+This section covers the current UDS Arquiceture & diagrams.
+
+UDS is built on the Django web framework, which itself is
+built on Python, thus MyTARDIS follows the architectural model
+of Django.
+
+Component Architecture
+----------------------
+
+This diagram shows the major components of UDS.
+
+* Core components
+ * `Apache Http `_
+ * `WSGI `_
+ * `Django `_
+ * `Python `_.
+
+* RDBMS
+ UDS is currently being developed/testing on Mysql 5 Database.
+ May other databases will work also, but no one else has been tested.
+
+Functional Architecture
+-----------------------
+
+UDS is build using Django as base support for Web acess and Database access.
+
+Over this, UDS uses the following diagram:
+
+DIAGRAM
+
+Core
+ Basic core funcionality.
diff --git a/trunk/server/documentation/development/contributing.rst b/trunk/server/documentation/development/contributing.rst
new file mode 100644
index 00000000..ec04f5a4
--- /dev/null
+++ b/trunk/server/documentation/development/contributing.rst
@@ -0,0 +1,3 @@
+===================
+Contributing to UDS
+===================
diff --git a/trunk/server/documentation/development/repository.rst b/trunk/server/documentation/development/repository.rst
new file mode 100644
index 00000000..36b5e39b
--- /dev/null
+++ b/trunk/server/documentation/development/repository.rst
@@ -0,0 +1,3 @@
+==============
+UDS Repository
+==============
\ No newline at end of file
diff --git a/trunk/server/documentation/development/samples/auths/Authenticator.rst b/trunk/server/documentation/development/samples/auths/Authenticator.rst
new file mode 100644
index 00000000..f4394981
--- /dev/null
+++ b/trunk/server/documentation/development/samples/auths/Authenticator.rst
@@ -0,0 +1,15 @@
+====================
+Sample Authenticator
+====================
+
+The authenticator is the responsible of providing the needed mechanisms to UDS for
+user authentication.
+
+As thatm this must provide a number of methods, that will allow UDS to manage
+things the way it needs to. (Access users, groups, check credentials, etc...)
+
+Here you can :download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/auths/SampleAuth.py
+ :linenos:
diff --git a/trunk/server/documentation/development/samples/samples.rst b/trunk/server/documentation/development/samples/samples.rst
new file mode 100644
index 00000000..604170bf
--- /dev/null
+++ b/trunk/server/documentation/development/samples/samples.rst
@@ -0,0 +1,100 @@
+===================
+UDS Modules Samples
+===================
+
+In this section we cover basic samples of the different kind of mudules supported
+by UDS.
+
+UDS is designed in a modular way, meaning this that it has a core that allows
+a number of modules to get plugged inside the whole system.
+
+This modules are:
+
+ * Services, including all stuff around them.
+ * Transports
+ * OS Managers
+ * Authenticators
+
+This secion will try to give sample of every module, what it must do and how this
+must be done.
+
+Service Sample
+--------------
+
+A service is composed of several classes. This classes depends on how the service works.
+
+This are:
+
+ * *Provider*, that is simply the "root" where services
+ descent, so we can configure just one part of the service parameters and rest
+ of them at service level.
+
+ One sample of provider is a virtualization server, such as oVirt, Open Nebula, or
+ others like it. We can keep info about server at provider level, and info about
+ what we need in an specific service at service level.
+
+ * *Service*, that is a service definition, that must be deployed at a later stage
+ to offer something to the users.
+
+ Following our previous sample, if provider was an oVirt server, a service can
+ be a Virtual Machine cloned COW.
+
+ * *Publication*, This class is optional. If service declares that needs a
+ publication for deployment of user instance, this class implements exactly
+ that, the publication for that service. Publications are in fact a way of
+ allowing services to prepare something in a stage prior to creating the
+ user consumable services.
+
+ Following our previous sample, if provider was an oVirt Server and the service
+ was a Virtual Machine cloned for Cow, the poblication can be a full clone of
+ the service machine for making COWS from this one.
+
+ * *DeployedService*, This class is the user consumed service itself. After a
+ service is created, it must be deployed, and deploy will mean that there will
+ be "instances" of that service (User Deployments) that will be consumed by
+ users.
+
+ Following our previous sample, if the publication was a full copy machine,
+ an deployed service can be a machine in COW format using as base that
+ machine.
+
+
+From theese, the only not really needed is Publication. Publication will only be
+needed whenever a service needs a "preparation" before creating the user consumable
+deployed services. For a service to be usable, we will need the full tree, meaning
+this that we will provide all controllers (Provider, service or services, publication
+or publications, deployed service or deployed services.).
+
+All class belonging to a service must be grouped under the same package, and we
+well need to register this package for the system to recognize it as service.
+
+For this, we must register the Provider, that has references to rest of items.
+
+Provider declares which services it provides. Services declares which publication
+and deployed service it needs. Provider can declare multiples services it offers,
+but services has at most one publication and exatly one deployed service.
+
+So, by registering the Provider, we register the whole tree provided by de package.
+
+Here you can find samples of every class needed for creating a new package of
+services.
+
+.. toctree::
+
+ services/whatisneeded
+ services/Provider
+ services/Service
+ services/Publication
+ services/DeployedServiceOne
+ services/DeployedServiceTwo
+
+
+Authenticator Sample
+--------------------
+
+An authenticator is composed of a single class, derived from :py:class:`uds.core.auths.Authenticator`.
+
+Here you can find a sample of an authenticator.
+
+.. toctree::
+ auths/Authenticator
\ No newline at end of file
diff --git a/trunk/server/documentation/development/samples/services/DeployedServiceOne.rst b/trunk/server/documentation/development/samples/services/DeployedServiceOne.rst
new file mode 100644
index 00000000..abf2b43d
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/DeployedServiceOne.rst
@@ -0,0 +1,20 @@
+==========================
+Sample User Deployment One
+==========================
+
+User deployments are the class that are responsible for creating the ultimate consumable
+user service, that is, for managing that whenever the core requests a new service for
+an user, this classes will take responsibility to provide it.
+
+Here we cover SampleUserDeploymentOne that is for SampleServiceOne, do not needs to be
+published and do not uses cache.
+
+You can easily follow the code to see what it does, and what you have to do if you
+want to provide a new one.
+
+:download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/services/SampleUserDeploymentOne.py
+ :linenos:
+
diff --git a/trunk/server/documentation/development/samples/services/DeployedServiceTwo.rst b/trunk/server/documentation/development/samples/services/DeployedServiceTwo.rst
new file mode 100644
index 00000000..f3664a74
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/DeployedServiceTwo.rst
@@ -0,0 +1,20 @@
+==========================
+Sample User Deployment Two
+==========================
+
+User deployments are the class that are responsible for creating the ultimate consumable
+user service, that is, for managing that whenever the core requests a new service for
+an user, this classes will take responsibility to provide it.
+
+Here we cover SampleUserDeploymentTwo that is for SampleServiceTwo, needs to be
+published and has L1 and L2 cache items.
+
+You can easily follow the code to see what it does, and what you have to do if you
+want to provide a new one.
+
+:download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/services/SampleUserDeploymentTwo.py
+ :linenos:
+
diff --git a/trunk/server/documentation/development/samples/services/Provider.rst b/trunk/server/documentation/development/samples/services/Provider.rst
new file mode 100644
index 00000000..b792a642
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/Provider.rst
@@ -0,0 +1,19 @@
+=======================
+Sample Service Provider
+=======================
+
+The service provider is the top of the tree of services needed clases.
+It main function is to provide a base for services, where this services contains
+a common parent that is, for example, a server, a range of IPs, etc...
+
+This sample covers a simple service provider, explains also a bit about FormFields
+and shows what tasks must be done by a service provider.
+
+You can easily follow the code to see what it does, and what you have to do if you
+want to provide a new one.
+
+:download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/services/SampleProvider.py
+ :linenos:
diff --git a/trunk/server/documentation/development/samples/services/Publication.rst b/trunk/server/documentation/development/samples/services/Publication.rst
new file mode 100644
index 00000000..7d82cc0c
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/Publication.rst
@@ -0,0 +1,23 @@
+==================
+Sample publication
+==================
+
+A publication is a class responsible for making a service defined available to be
+consumed by users.
+
+Not all services needs publications as you have already seen if you are following
+the samples. Publications are only needed for services that needs some kind of
+preparation, as, for example, with Virtual Machines, clone the base virtual machine
+so we can create COW copies from this clone. This kind of behavior needs a preparation
+step, that is efectively to clone the virtual base, and that will be the task of a
+publication for that kind of services.
+
+You can easily follow the code to see what it does, and what you have to do if you
+want to provide a new one.
+
+:download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/services/SamplePublication.py
+ :linenos:
+
diff --git a/trunk/server/documentation/development/samples/services/Service.rst b/trunk/server/documentation/development/samples/services/Service.rst
new file mode 100644
index 00000000..49482e70
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/Service.rst
@@ -0,0 +1,15 @@
+==============
+Sample service
+==============
+
+Here we cover two services. ServiceOne, that do not needs publication and
+ServiceTwo, that needs publication.
+
+This sample should be enought to guide you through the creation of a new service.
+
+:download:`Download sample `
+
+
+.. literalinclude:: /_downloads/samples/services/SampleService.py
+ :linenos:
+
diff --git a/trunk/server/documentation/development/samples/services/whatisneeded.rst b/trunk/server/documentation/development/samples/services/whatisneeded.rst
new file mode 100644
index 00000000..20eb9ffb
--- /dev/null
+++ b/trunk/server/documentation/development/samples/services/whatisneeded.rst
@@ -0,0 +1,32 @@
+Needs for a service package
+---------------------------
+
+For a new package of services, you will need:
+
+
+ * One package (python package), of course :-).
+ * One icon for the provider, in png format an 16x16 size. Colours is left
+ to your election. This icon will be informed at Provider class.
+ * One icon for every service that the provider will expose. Same as provider
+ icons. These icons will be informed at Service class. Every single class
+ must provide its own icon.
+ * Registering the provider. For the samples show here, this will be at
+ __init__ of the package.
+
+ The contents of the sample package __init__ file is:
+
+ .. literalinclude:: /_downloads/samples/services/__init__.py
+ :linenos:
+
+ :download:`Download sample `
+
+ * Put the package under the apropiate uds package. In the case of
+ services, this is under "uds.core".
+
+ Core will look for all packages under "uds.services" and import them at
+ initialization of the server, so every package under this will get their
+ __init__ called, where we register the provider.
+
+ * Follow the samples provided here as base
+
+
\ No newline at end of file
diff --git a/trunk/server/documentation/index.rst b/trunk/server/documentation/index.rst
new file mode 100644
index 00000000..b7e3f500
--- /dev/null
+++ b/trunk/server/documentation/index.rst
@@ -0,0 +1,71 @@
+.. _index:
+
+===================
+UDS's documentation
+===================
+
+This documentation is provided so we can understand (hopefully) UDS, its internals,
+and everything about it.
+
+Right now the documentation is not too ritch, but we are working on it so it will
+get the needed level for this kind of project.
+
+
+First Steps
+===========
+
+* **From scratch:**
+ :doc:`Overview ` |
+ :doc:`Installation `
+
+.. toctree::
+ :hidden:
+
+ intro/overview
+ intro/install
+
+The internals of uds
+====================
+
+.. toctree::
+
+ development/architecture
+ development/development
+ api/index
+
+UDS Open source project
+=======================
+
+* **Community:**
+ :doc:`How to get involved ` |
+ :doc:`The UDS source code repository `
+
+.. toctree::
+ :hidden:
+
+ development/contributing
+ development/repository
+
+
+Acknowledgements
+================
+
+We want to thaks all the people that has contributed to de project, an also
+other Open Source project used to improve this one.
+
+List of other software used to build UDS:
+
+ * `Django `_
+ * `XML-RPC.NET Copyright (c) 2006 Charles Cook `_
+ * `Darkglass reworked graphics `_
+ * `Crystal project `_
+ * `South `_
+ * `Jsch `_
+ * `JQuery `_
+ * `Plugin detect library `_
+ * `JQuery UI `_
+
+I hope to do nor forget anythinh here, if i do, please, report it so we can credit
+to every project that UDS makes use of.
+
+
\ No newline at end of file
diff --git a/trunk/server/documentation/intro/install.rst b/trunk/server/documentation/intro/install.rst
new file mode 100644
index 00000000..39b17dcb
--- /dev/null
+++ b/trunk/server/documentation/intro/install.rst
@@ -0,0 +1,46 @@
+==============
+Installing UDS
+==============
+
+In order to run UDS, you will need:
+
+ * Django Server 1.4
+ * South module for Django
+ * Mysql libraries for python
+ * Mysql Database
+ * Ldap Libraries for python
+ * Criptographic package for python
+
+Default transports are compiled in binary form, and keeped inside UDS repository,
+so you won't need Java to put UDS to work.
+
+Once you have all of this, you will have to follow these steps:
+
+ * Obtain UDS from repository, you can see how to do this from
+:doc:`repository access documentation `
+ * Configure a database for use with UDS. To do this, simple create a database
+ inside your Mysql server, and a user with all permissions in this database.
+ * Configure UDS settings.
+ Inside "server" folder, you will find "settings.py". This file contains the
+ configuration of UDS (if it runs in debug mode, ..). The most important part
+ here is the DATABASES section, where you will set up the database that UDS
+ will use. Simply change "host", "port", "udsername", "password" and "name"
+ to match your database settings.
+ Here, we have to take care that, if we left UDS in debug mode, Django will keep
+ track of all petitions to UDS, so memory will grow constantly. Do not get scared
+ if you see that UDS starts to waste too much memory. Simply restart it or, if it's
+ intended to be running for a while, set DEBUG variable to "False".
+ Important sections are:
+
+ * Create initial database tables.
+ Inside UDS folder, where you downloaded it, you will see a "manage.py".
+ This python application is the responsible for managing UDS, from database creation,
+ migrations, backend start & stop, web server (testing web server btw), ...
+ To create initial databases, we will do:
+
+ python manage.py sync
+ python manage.py migrate
+
+ Now we have all databases and everything that UDS needs for starting up ready... :-)
+
+
diff --git a/trunk/server/documentation/intro/overview.rst b/trunk/server/documentation/intro/overview.rst
new file mode 100644
index 00000000..2e8705d7
--- /dev/null
+++ b/trunk/server/documentation/intro/overview.rst
@@ -0,0 +1,32 @@
+===============
+UDS at a glance
+===============
+
+UDS has been developed to make a single open source server that allows the access
+to the growing ip services catalog.
+
+For this, we have try to make a framework that allows the use of any ip service,
+focusing initially at VDI because it's the mayor need for the people we have
+contacted initially .
+
+Also, first version of UDS has been developed "fast" (very fast indeed), so now
+we need to make a revision an adapt de code of the framework so it's more
+'pythonic'. (Think that i start learning python one day like this, and less than
+a week later i started this proyect). So think that, althouth UDS is fully
+functional, has been tested and is stable enought for any production environment,
+there is a lot of work to do.
+
+As so, UDS not only provides default modules for a lot of things (virtualization
+provider, authentication providers, protocols, ...), but also provides the core
+itself to allow anyone who wants or needs something, incorporate it to the
+catalog of UDS in an easy and fast way.
+
+* In order to use UDS, you must simply :doc:`Follow the installation guide `.
+
+* In order to design and implement your own modules, you must:
+
+ * :doc:`Understand the architecture `
+ * :doc:`See some module samples `
+
+* In order to contribute, you must install UDS, understand it, an read the
+ :doc:`contributing guide `
\ No newline at end of file
diff --git a/trunk/server/documentation/make.bat b/trunk/server/documentation/make.bat
new file mode 100644
index 00000000..3ebfb5f0
--- /dev/null
+++ b/trunk/server/documentation/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^` where ^ is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\UDS.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\UDS.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/trunk/server/src/manage.py b/trunk/server/src/manage.py
new file mode 100755
index 00000000..fa9aa7af
--- /dev/null
+++ b/trunk/server/src/manage.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+import os, sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/trunk/server/src/server/__init__.py b/trunk/server/src/server/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/server/settings.py.sample b/trunk/server/src/server/settings.py.sample
new file mode 100644
index 00000000..52a16f0c
--- /dev/null
+++ b/trunk/server/src/server/settings.py.sample
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+'''
+Settings file for uds server (Django)
+'''
+
+import os
+import django
+import django.conf.global_settings as DEFAULT_SETTINGS
+
+# calculated paths for django and the site
+# used as starting points for various other paths
+DJANGO_ROOT = os.path.dirname(os.path.realpath(django.__file__))
+SITE_ROOT = '/'.join(os.path.dirname(os.path.realpath(__file__)).split('/')[:-1])
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+#USE_X_FORWARDED_HOST = True
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # For testing begind a reverse proxy
+
+# This mark can be used by a setup script to easy locate DB Section of the settings file
+#DB_SECTION_START
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'OPTIONS': {
+ #'init_command': 'SET storage_engine=INNODB, SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',
+ 'init_command' : 'SET storage_engine=MYISAM, SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',
+ },
+ 'NAME': 'dbuds', # Or path to database file if using sqlite3.
+ 'USER': 'dbuds', # Not used with sqlite3.
+ 'PASSWORD': 'dbuds', # Not used with sqlite3.
+ 'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '3306', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+#DB_SECTION_END
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+
+#TIME_SECTION_START
+TIME_ZONE = 'Europe/Madrid'
+#TIME_SECTION_END
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en'
+
+ugettext = lambda s: s
+
+LANGUAGES = (
+ ('es', ugettext('Spanish')),
+ ('en', ugettext('English')),
+ ('fr', ugettext('French')),
+ ('de', ugettext('German')),
+)
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+#ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+#KEYS_SECTION_START
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 's5ky!7b5f#s35!e38xv%e-+iey6yi-#630x)tm1hf6_j8rie2*'
+# This is a very long string, an RSA KEY (this can be changed, but if u loose it, all encription will be lost)
+RSA_KEY = '-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQC0qe1GlriQbHFYdKYRPBFDSS8Ne/TEKI2mtPKJf36XZTy6rIyH\nvUpT1gMScVjHjOISLNJQqktyv0G+ZGzLDmfkCUBev6JBlFwNeX3Dv/97Q0BsEzJX\noYHiDANUkuB30ukmGvG0sg1v4ccl+xs2Su6pFSc5bGINBcQ5tO0ZI6Q1nQIDAQAB\nAoGBAKA7Octqb+T/mQOX6ZXNjY38wXOXJb44LXHWeGnEnvUNf/Aci0L0epCidfUM\nfG33oKX4BMwwTVxHDrsa/HaXn0FZtbQeBVywZqMqWpkfL/Ho8XJ8Rsq8OfElrwek\nOCPXgxMzQYxoNHw8V97k5qhfupQ+h878BseN367xSyQ8plahAkEAuPgAi6aobwZ5\nFZhx/+6rmQ8sM8FOuzzm6bclrvfuRAUFa9+kMM2K48NAneAtLPphofqI8wDPCYgQ\nTl7O96GXVQJBAPoKtWIMuBHJXKCdUNOISmeEvEzJMPKduvyqnUYv17tM0JTV0uzO\nuDpJoNIwVPq5c3LJaORKeCZnt3dBrdH1FSkCQQC3DK+1hIvhvB0uUvxWlIL7aTmM\nSny47Y9zsc04N6JzbCiuVdeueGs/9eXHl6f9gBgI7eCD48QAocfJVygphqA1AkEA\nrvzZjcIK+9+pJHqUO0XxlFrPkQloaRK77uHUaW9IEjui6dZu4+2T/q7SjubmQgWR\nZy7Pap03UuFZA2wCoqJbaQJAUG0FVrnyUORUnMQvdDjAWps2sXoPvA8sbQY1W8dh\nR2k4TCFl2wD7LutvsdgdkiH0gWdh5tc1c4dRmSX1eQ27nA==\n-----END RSA PRIVATE KEY-----'
+#KEYS_SECTION_END
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+# Own context processors plus django's onw
+TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
+ 'uds.core.util.Config.context_processor',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+
+SESSION_COOKIE_HTTPONLY = False
+
+ROOT_URLCONF = 'server.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(SITE_ROOT, 'templates')
+)
+
+INSTALLED_APPS = (
+ #'django.contrib.contenttypes', # Not used
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'south',
+ 'uds',
+)
+
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGDIR = SITE_ROOT + '/' + 'log'
+LOGFILE = 'uds.log'
+SERVICESFILE = 'services.log'
+AUTHFILE = 'auth.log'
+USEFILE = 'use.log'
+LOGLEVEL = DEBUG and 'DEBUG' or 'INFO'
+ROTATINGSIZE = 32*1024*1024 # 32 Megabytes before rotating files
+
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': True,
+ 'formatters': {
+ 'verbose': {
+ 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
+ },
+ 'simple': {
+ 'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
+ },
+ 'database': {
+ 'format': '%(levelname)s %(asctime)s Database %(message)s'
+ },
+ 'auth': {
+ 'format': '%(asctime)s %(message)s'
+ },
+ 'use': {
+ 'format': '%(asctime)s %(message)s'
+ }
+ },
+ 'handlers': {
+ 'null': {
+ 'level':'DEBUG',
+ 'class':'django.utils.log.NullHandler',
+ },
+
+ 'file':{
+ 'level':'DEBUG',
+ 'class':'logging.handlers.RotatingFileHandler',
+ 'formatter': 'simple',
+ 'filename': LOGDIR + '/' + LOGFILE,
+ 'mode': 'a',
+ 'maxBytes': ROTATINGSIZE,
+ 'backupCount': 3,
+ 'encoding': 'utf-8'
+ },
+
+ 'servicesFile':{
+ 'level':'DEBUG',
+ 'class':'logging.handlers.RotatingFileHandler',
+ 'formatter': 'simple',
+ 'filename': LOGDIR + '/' + SERVICESFILE,
+ 'mode': 'a',
+ 'maxBytes': ROTATINGSIZE,
+ 'backupCount': 3,
+ 'encoding': 'utf-8'
+ },
+
+ 'authFile':{
+ 'level':'DEBUG',
+ 'class':'logging.handlers.RotatingFileHandler',
+ 'formatter': 'auth',
+ 'filename': LOGDIR + '/' + AUTHFILE,
+ 'mode': 'a',
+ 'maxBytes': ROTATINGSIZE,
+ 'backupCount': 3,
+ 'encoding': 'utf-8'
+ },
+
+ 'useFile':{
+ 'level':'DEBUG',
+ 'class':'logging.handlers.RotatingFileHandler',
+ 'formatter': 'use',
+ 'filename': LOGDIR + '/' + USEFILE,
+ 'mode': 'a',
+ 'maxBytes': ROTATINGSIZE,
+ 'backupCount': 3,
+ 'encoding': 'utf-8'
+ },
+
+ 'console':{
+ 'level':'DEBUG',
+ 'class':'logging.StreamHandler',
+ 'formatter': 'simple'
+ },
+ 'database':{
+ 'level':'DEBUG',
+ 'class':'logging.StreamHandler',
+ 'formatter': 'database'
+ },
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler',
+ }
+ },
+ 'loggers': {
+ 'django': {
+ 'handlers':['null'],
+ 'propagate': True,
+ 'level':'INFO',
+ },
+ 'django.request': {
+ 'handlers': ['file'],
+ 'level': 'ERROR',
+ 'propagate': False,
+ },
+ 'django.db.backends': {
+ 'handlers': ['database'],
+ 'level': 'ERROR',
+ 'propagate': False,
+ },
+
+ 'uds': {
+ 'handlers': ['file'],
+ 'level': LOGLEVEL,
+ },
+
+ 'uds.services': {
+ 'handlers': ['servicesFile'],
+ 'level': LOGLEVEL,
+ 'propagate': False,
+ },
+ # Custom Auth log
+ 'authLog': {
+ 'handlers' : ['authFile'],
+ 'level': 'INFO',
+ 'propagate': False,
+ },
+ # Custom Services use log
+ 'useLog': {
+ 'handlers' : ['useFile'],
+ 'level': 'INFO',
+ 'propagate': False,
+ }
+
+ }
+}
diff --git a/trunk/server/src/server/urls.py b/trunk/server/src/server/urls.py
new file mode 100644
index 00000000..822d5f7e
--- /dev/null
+++ b/trunk/server/src/server/urls.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+'''
+Url patterns for UDS project (Django)
+'''
+
+from django.conf.urls.defaults import patterns, include
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^', include('uds.urls'))
+)
diff --git a/trunk/server/src/server/wsgi.py b/trunk/server/src/server/wsgi.py
new file mode 100644
index 00000000..116bd6ab
--- /dev/null
+++ b/trunk/server/src/server/wsgi.py
@@ -0,0 +1,38 @@
+"""
+WSGI config for server project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import sys
+reload(sys)
+sys.setdefaultencoding('UTF-8')
+
+import os
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
+
+# This application object is used by any WSGI server configured to use this
+# file. This includes Django's development server, if the WSGI_APPLICATION
+# setting points here.
+# Django 1.4
+#from django.core.wsgi import get_wsgi_application
+#application = get_wsgi_application()
+
+# Django 1.3
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
+
+
+# Apply WSGI middleware here.
+# from helloworld.wsgi import HelloWorldApplication
+# application = HelloWorldApplication(application)
diff --git a/trunk/server/src/sitecustomize.py b/trunk/server/src/sitecustomize.py
new file mode 100644
index 00000000..0ada9861
--- /dev/null
+++ b/trunk/server/src/sitecustomize.py
@@ -0,0 +1,3 @@
+import sys
+
+sys.setdefaultencoding('UTF-8')
diff --git a/trunk/server/src/uds/__init__.py b/trunk/server/src/uds/__init__.py
new file mode 100644
index 00000000..4d75d3d9
--- /dev/null
+++ b/trunk/server/src/uds/__init__.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+
+
+from django.dispatch import dispatcher
+from django.db.models import signals
+
+# Make sure that all services are "available" at service startup
+import services # to make sure that the packages are initialized at this point
+import auths # To make sure that the packages are initialized at this point
+import osmanagers # To make sure that packages are initialized at this point
+import transports # To make sure that packages are initialized at this point
+import dispatchers
+import models
+
+
+def modify_MySQL_storage(sender, **kwargs):
+ from django.db import connection
+ cursor = connection.cursor()
+
+ innoDbTables = ( models.UserService, models.DeployedService, models.DeployedServicePublication,
+ models.Scheduler, models.DelayedTask, )
+ dicTables = { k._meta.db_table: True for k in innoDbTables }
+
+ for model in kwargs['created_models']:
+ db_table=model._meta.db_table
+ if dicTables.has_key(db_table):
+ stmt = 'ALTER TABLE %s ENGINE=%s' % (db_table,'InnoDB')
+ cursor.execute(stmt)
+ # sets charset to utf8
+ stmt = 'ALTER TABLE %s CHARACTER SET \'utf8\' COLLATE \'utf8_general_ci\'' % db_table
+ cursor.execute(stmt)
+
+
+signals.post_syncdb.connect(modify_MySQL_storage, sender=models)
diff --git a/trunk/server/src/uds/auths/IP/Authenticator.py b/trunk/server/src/uds/auths/IP/Authenticator.py
new file mode 100644
index 00000000..826c3dd3
--- /dev/null
+++ b/trunk/server/src/uds/auths/IP/Authenticator.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as _
+from uds.core.auths import Authenticator
+from uds.core.auths.GroupsManager import GroupsManager
+from uds.models import Network
+from uds.core.util.Config import Config
+import logging, random, string
+
+logger = logging.getLogger(__name__)
+
+class IPAuth(Authenticator):
+ typeName = _('IP Authenticator')
+ typeType = 'IPAuth'
+ typeDescription = _('IP Authenticator')
+ iconFile = 'auth.png'
+
+ needsPassword = False
+ userNameLabel = _('IP')
+ groupNameLabel = _('IP Range')
+ isExternalSource = True
+
+ def __init__(self, dbAuth, environment, values = None):
+ super(IPAuth, self).__init__(dbAuth, environment, values)
+ # Ignore values
+
+ def valuesDict(self):
+ res = {}
+ return res
+
+ def __str__(self):
+ return "Internal IP Authenticator"
+
+ def marshal(self):
+ return "v1"
+
+ def unmarshal(self, str_):
+ data = str_.split('\t')
+ if data[0] == 'v1':
+ pass
+
+ def getGroups(self, ip, groupsManager):
+ # these groups are a bit special. They are in fact ip-ranges, and we must check that the ip is in betwen
+ # The ranges are stored in group names
+ ip = Network.ipToLong(ip)
+ g = []
+ for g in groupsManager.getGroupsNames():
+ rangeStart, rangeEnd = g.split('-')
+ rangeStart = Network.ipToLong(rangeStart)
+ rangeEnd = Network.ipToLong(rangeEnd)
+ if ip >= rangeStart and ip <= rangeEnd:
+ groupsManager.validate(g)
+
+ def authenticate(self, username, credentials, groupsManager):
+ # If credentials is a dict, that can't be sent directly from web interface, we allow entering
+ # We use this "trick" so authenticators
+ if self.cache().get(username) == credentials:
+ self.cache().remove(username)
+ self.getGroups(username, groupsManager)
+ return True
+ return False
+
+ def internalAuthenticate(self,username, credentials, groupsManager):
+ self.getGroups(username, groupsManager)
+ if len(groupsManager.getValidGroups()) > 0 and self.dbAuthenticator().isValidUser(username, True):
+ return True
+ return False
+
+ @staticmethod
+ def test(env, data):
+ return "Internal structures seems ok"
+
+ def check(self):
+ return _("All seems fine in the authenticator.")
+
+ def getHtml(self, request):
+ # doAutoLogin = Config.section('IPAUTH').value('autoLogin', '0').getBool()
+ gm = GroupsManager(self.dbAuthenticator())
+ self.getGroups(request.ip, gm)
+ if len(gm.getValidGroups()) > 0 and self.dbAuthenticator().isValidUser(request.ip, True):
+ passw = ''.join(random.choice(string.letters + string.digits) for __ in xrange(12))
+ self.cache().put(request.ip, passw)
+ return ''
+ else:
+ return 'This ip is not allowed to autologin (' + request.ip +')
'
+ # We will authenticate ip here, from request.ip
+ # If valid, it will simply submit form with ip submited and a cached generated random password
diff --git a/trunk/server/src/uds/auths/IP/__init__.py b/trunk/server/src/uds/auths/IP/__init__.py
new file mode 100644
index 00000000..5c267fcd
--- /dev/null
+++ b/trunk/server/src/uds/auths/IP/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core.util.Config import Config
+from Authenticator import IPAuth
+
+# Access configuration value as soon as we can, so it is available at db
+Config.section('IPAUTH').value('autoLogin', '0').get() # If 1, try to autologin
+
diff --git a/trunk/server/src/uds/auths/IP/auth.png b/trunk/server/src/uds/auths/IP/auth.png
new file mode 100644
index 00000000..9ec2bc52
Binary files /dev/null and b/trunk/server/src/uds/auths/IP/auth.png differ
diff --git a/trunk/server/src/uds/auths/InternalDB/Authenticator.py b/trunk/server/src/uds/auths/InternalDB/Authenticator.py
new file mode 100644
index 00000000..08c1355d
--- /dev/null
+++ b/trunk/server/src/uds/auths/InternalDB/Authenticator.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as _
+from uds.core.auths import Authenticator
+from uds.core.managers.CryptoManager import CryptoManager
+from uds.models import Authenticator as dbAuthenticator
+from uds.core.util.State import State
+import hashlib
+import logging
+
+logger = logging.getLogger(__name__)
+
+class InternalDBAuth(Authenticator):
+ typeName = _('Internal Database')
+ typeType = 'InternalDBAuth'
+ typeDescription = _('Internal dabasase authenticator. Doesn\'t uses external sources')
+ iconFile = 'auth.png'
+
+
+ # If we need to enter the password for this user
+ needsPassword = True
+
+ # This is the only internal source
+ isExternalSource = False
+
+
+ def __init__(self, dbAuth, environment, values = None):
+ super(InternalDBAuth, self).__init__(dbAuth, environment, values)
+ # Ignore values
+
+ def valuesDict(self):
+ res = {}
+ return res
+
+ def __str__(self):
+ return "Internal DB Authenticator Authenticator"
+
+ def marshal(self):
+ return "v1"
+
+ def unmarshal(self, str_):
+ data = str_.split('\t')
+ if data[0] == 'v1':
+ pass
+
+ def authenticate(self, username, credentials, groupsManager):
+ logger.debug('Username: {0}, Password: {1}'.format(username, credentials))
+ auth = self.dbAuthenticator()
+ try:
+ usr = auth.users.filter(name=username, state=State.ACTIVE)
+ if len(usr) == 0:
+ return False
+ usr = usr[0]
+ # Internal Db Auth has its own groups, and if it active it is valid
+ if usr.password == hashlib.sha1(credentials).hexdigest():
+ groupsManager.validate([g.name for g in usr.groups.all()])
+ return True
+ return False
+ except dbAuthenticator.DoesNotExist:
+ return False
+
+ def createUser(self, usrData):
+ pass
+
+ @staticmethod
+ def test(env, data):
+ return [True, _("Internal structures seems ok")]
+
+ def check(self):
+ return _("All seems fine in the authenticator.")
+
+
+
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/auths/InternalDB/__init__.py b/trunk/server/src/uds/auths/InternalDB/__init__.py
new file mode 100644
index 00000000..94105fc9
--- /dev/null
+++ b/trunk/server/src/uds/auths/InternalDB/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from Authenticator import InternalDBAuth
+
diff --git a/trunk/server/src/uds/auths/InternalDB/auth.png b/trunk/server/src/uds/auths/InternalDB/auth.png
new file mode 100644
index 00000000..9ec2bc52
Binary files /dev/null and b/trunk/server/src/uds/auths/InternalDB/auth.png differ
diff --git a/trunk/server/src/uds/auths/RegexLdap/Authenticator.py b/trunk/server/src/uds/auths/RegexLdap/Authenticator.py
new file mode 100644
index 00000000..f5bbc09d
--- /dev/null
+++ b/trunk/server/src/uds/auths/RegexLdap/Authenticator.py
@@ -0,0 +1,385 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core.auths import Authenticator
+import ldap, re
+
+import logging
+from uds.core.auths.Exceptions import AuthenticatorException
+
+logger = logging.getLogger(__name__)
+
+LDAP_RESULT_LIMIT = 50
+
+class RegexLdap(Authenticator):
+
+ host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('VMWare VC Server IP or Hostname'), required = True)
+ port = gui.NumericField(length=5, label = _('Port'), defvalue = '389', order = 2, tooltip = _('Ldap port (389 for non ssl, 636 for ssl normally'), required = True)
+ ssl = gui.CheckBoxField(label = _('Use SSL'), order = 3, tooltip = _('If checked, will use a ssl connection to ldap (if port is 389, will use in fact port 636)'))
+ username = gui.TextField(length=64, label = _('Ldap User'), order = 4, tooltip = _('Username with read privileges on the base selected'), required = True)
+ password = gui.PasswordField(lenth=32, label = _('Password'), order = 5, tooltip = _('Password of the ldap user'), required = True)
+ timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 6, tooltip = _('Timeout in seconds of connection to LDAP'), required = True)
+ ldapBase = gui.TextField(length=64, label = _('Base'), order = 7, tooltip = _('Common search base (used for "users" and "groups"'), required = True)
+ userClass = gui.TextField(length=64, label = _('User class'), defvalue = 'posixAccount', order = 8, tooltip = _('Class for LDAP users (normally posixAccount)'), required = True)
+ userIdAttr = gui.TextField(length=64, label = _('User Id Attr'), defvalue = 'uid', order = 9, tooltip = _('Attribute that contains the user id'), required = True)
+ userNameAttr = gui.TextField(length=64, label = _('User Name Attr'), defvalue = 'uid', order = 10, tooltip = _('Attributes that contains the user name (list of comma separated values)'), required = True)
+ groupNameAttr = gui.TextField(length=64, label = _('Group Name Attr'), defvalue = 'cn', order = 11, tooltip = _('Attribute that contains the group name'), required = True)
+ regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True)
+
+ typeName = _('Regex LDAP Authenticator')
+ typeType = 'RegexLdapAuthenticator'
+ typeDescription = _('Regular Expressions LDAP authenticator')
+ iconFile = 'auth.png'
+
+ # If it has and external source where to get "new" users (groups must be declared inside UDS)
+ isExternalSource = True
+ # If we need to enter the password for this user
+ needsPassword = False
+ # Label for username field
+ userNameLabel = _('Username')
+ # Label for group field
+ groupNameLabel = _("Group")
+ # Label for password field
+ passwordLabel = _("Password")
+
+ def __init__(self, dbAuth, environment, values = None):
+ super(RegexLdap, self).__init__(dbAuth, environment, values)
+ if values != None:
+ self._host = values['host']
+ self._port = values['port']
+ self._ssl = gui.strToBool(values['ssl'])
+ self._username = values['username']
+ self._password = values['password']
+ self._timeout = values['timeout']
+ self._ldapBase = values['ldapBase']
+ self._userClass = values['userClass']
+ self._userIdAttr = values['userIdAttr']
+ self._groupNameAttr = values['groupNameAttr']
+ self._regex = values['regex']
+ self._userNameAttr = values['userNameAttr']
+ try:
+ re.search(self._regex, '')
+ except:
+ raise Authenticator.ValidationException(_('Invalid regular expression'))
+ else:
+ self._host = None
+ self._port = None
+ self._ssl = None
+ self._username = None
+ self._password = None
+ self._timeout = None
+ self._ldapBase = None
+ self._userClass = None
+ self._userIdAttr = None
+ self._groupNameAttr = None
+ self._regex = None
+ self._userNameAttr = None
+ self._connection = None
+
+ def valuesDict(self):
+ return { 'host' : self._host, 'port' : self._port, 'ssl' : gui.boolToStr(self._ssl),
+ 'username' : self._username, 'password' : self._password, 'timeout' : self._timeout,
+ 'ldapBase' : self._ldapBase, 'userClass' : self._userClass,
+ 'userIdAttr' : self._userIdAttr, 'groupNameAttr' : self._groupNameAttr, 'regex' : self._regex,
+ 'userNameAttr' : self._userNameAttr
+ }
+
+ def __str__(self):
+ return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, userIdAttr = {6}, groupNameAttr = {7}, reg.ex. = {8}, userName attr = {9}".format(
+ self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._regex,
+ self._userNameAttr)
+
+ def marshal(self):
+ return str.join('\t', ['v1',
+ self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
+ self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._regex, self._userNameAttr ])
+
+ def unmarshal(self, val):
+ data = val.split('\t')
+ if data[0] == 'v1':
+ logger.debug("Data: {0}".format(data[1:]))
+ self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._regex, self._userNameAttr = data[1:]
+ self._ssl = gui.strToBool(self._ssl)
+
+ def __connection(self, username = None, password = None):
+ if self._connection is None or username is not None: # We want this method also to check credentials
+ l = None
+ cache = False
+ try:
+ #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9)
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
+ schema = self._ssl and 'ldaps' or 'ldap'
+ port = self._port != '389' and ':' + self._port or ''
+ uri = "%s://%s%s" % (schema, self._host, port)
+ logger.debug('Ldap uri: {0}'.format(uri))
+ l = ldap.initialize(uri=uri)
+ l.network_timeout = l.timeout = int(self._timeout)
+ l.protocol_version = ldap.VERSION3
+
+ if username is None:
+ cache = True
+ username = self._username
+ password = self._password
+
+ l.simple_bind_s(who = username, cred = password)
+ except ldap.LDAPError, e:
+ str_ = _('Ldap connection error: ')
+ if type(e.message) == dict:
+ str_ += e.message.has_key('info') and e.message['info'] + ',' or ''
+ str_ += e.message.has_key('desc') and e.message['desc'] or ''
+ else :
+ str_ += str(e)
+ raise Exception(str_)
+ if cache is True:
+ self._connection = l
+ else:
+ return l # Do not cache nor overwrite "global" connection
+ return self._connection
+
+ def __getUser(self, username):
+ try:
+ con = self.__connection()
+ filter = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, username)
+ attrlist = self._userNameAttr.split(',') + [self._userIdAttr, self._groupNameAttr]
+ logger.debug('Getuser filter: {0}, attr list: {1}'.format(filter, attrlist))
+ res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
+ filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
+ usr = dict(( k, '' ) for k in attrlist)
+ usr.update(res[1])
+ usr.update( {'dn' : res[0], '_id' : username })
+ logger.debug('Usr: {0}'.format(usr))
+ return usr
+ except Exception:
+ logger.exception('Exception:')
+ return None
+
+ def __getGroups(self, usr):
+ grps = usr[self._groupNameAttr]
+ if type(grps) is not list:
+ grps = [grps]
+ logger.debug("Groups: {0}".format(grps))
+ logger.debug("Re: {0}".format(self._regex))
+ regex = self._regex
+ if regex.find('(') == -1:
+ regex = '(' + regex + ')'
+ rg = re.compile(self._regex)
+ res = []
+ for g in grps:
+ ma = rg.search(g)
+ if ma is not None:
+ for m in ma.groups():
+ res.append(m)
+ logger.debug('Res: {0}'.format(res))
+ return res
+
+ def __getUserRealName(self, usr):
+ return ' '.join([ (type(usr.get(id_, '')) is list and ' '.join(( str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',') ]).strip()
+
+ def authenticate(self, username, credentials, groupsManager):
+ '''
+ Must authenticate the user.
+ We can have to different situations here:
+ 1.- The authenticator is external source, what means that users may be unknown to system before callig this
+ 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call
+ We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager.
+ The group manager is responsible for letting know the authenticator which groups we currently has active.
+ @see: uds.core.auths.GroupsManager
+ '''
+ try:
+ # Locate the user at LDAP
+ usr = self.__getUser(username)
+
+ if usr is None:
+ return False
+
+ # Let's see first if it credentials are fine
+ self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect
+
+ groupsManager.validate(self.__getGroups(usr))
+
+ return True
+
+ except Exception:
+ return False
+
+ def createUser(self, usrData):
+ '''
+ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
+ External sources already has the user cause they are managed externally, so, it can at most test if the users exists on external source
+ before accepting it.
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
+ @return: Raises an exception (AuthException) it things didn't went fine
+ '''
+ res = self.__getUser(usrData['name'])
+ if res is None:
+ raise AuthenticatorException(_('Username not found'))
+ # Fills back realName field
+ usrData['realName'] = self.__getUserRealName(res)
+
+
+ def getRealName(self, username):
+ '''
+ Tries to get the real name of an user
+ '''
+ res = self.__getUser(username)
+ if res is None:
+ return username
+ return self.__getUserRealName(res)
+
+ def modifyUser(self, usrData):
+ '''
+ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
+ Modify user has no reason on external sources, so it will never be used (probably)
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
+ @return: Raises an exception it things doesn't go fine
+ '''
+ return self.createUser(usrData)
+
+ def createGroup(self, groupData):
+ '''
+ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
+ External sources already has its own groups and, at most, it can check if it exists on external source before accepting it
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @params groupData: a dict that has, at least, name, comments and active
+ @return: Raises an exception it things doesn't go fine
+ '''
+ pass
+
+
+ def getGroups(self, username, groupsManager):
+ '''
+ Looks for the real groups to which the specified user belongs
+ Updates groups manager with valid groups
+ Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used)
+ '''
+ user = self.__getUser(username)
+ if user is None:
+ raise AuthenticatorException(_('Username not found'))
+ groups = self.__getGroups(user)
+ for g in groups:
+ gg = groupsManager.validate(g)
+
+ def searchUsers(self, pattern):
+ try:
+ con = self.__connection()
+ res = []
+ for r in con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
+ usrId = r[1].get(self._userIdAttr, '')
+ usrId = type(usrId) == list and usrId[0] or usrId
+ res.append( { 'id' : usrId,
+ 'name' : self.__getUserRealName(r[1]) } )
+ return res
+ except Exception, e:
+ logger.exception("Exception: ")
+ raise AuthenticatorException(_('Too many results, be more specific'))
+
+ @staticmethod
+ def test(env, data):
+ try:
+ auth = RegexLdap(None, env, data)
+ return auth.testConnection()
+ except Exception, e:
+ logger.error("Exception found testing Simple LDAP auth {0}: {1}".format(e.__class__, e))
+ return [False, "Error testing connection"]
+
+ def testConnection(self):
+ try:
+ con = self.__connection()
+ except Exception, e:
+ return [False, str(e)]
+
+ try:
+ con.search_s(base = self._ldapBase, scope = ldap.SCOPE_BASE)
+ except Exception:
+ return [False, _('Ldap search base is incorrect')]
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._userClass, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user class seems to be incorrect (no user found by that class)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap group class seems to be incorrect (no group found by that class)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._userIdAttr, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if self._groupNameAttr == 'dn':
+ raise Exception() # Can't search entries by dn, so this is not possible and dn is always retrieved
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._groupNameAttr, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ # Now test objectclass and attribute of users
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)')]
+ except Exception as e:
+ # If found 1 or more, all right
+ pass
+
+ # Now try to test regular expression to see if it matches anything (
+ try:
+ # Check the existence of at least a () grouping
+ # Check validity of regular expression (try to compile it)
+ # this only right now
+ pass
+ except Exception as e:
+ pass
+
+
+ return [True, _("Connection params seem correct, test was succesfully executed")]
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/auths/RegexLdap/__init__.py b/trunk/server/src/uds/auths/RegexLdap/__init__.py
new file mode 100644
index 00000000..3554b741
--- /dev/null
+++ b/trunk/server/src/uds/auths/RegexLdap/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from Authenticator import RegexLdap
+
diff --git a/trunk/server/src/uds/auths/RegexLdap/auth.png b/trunk/server/src/uds/auths/RegexLdap/auth.png
new file mode 100644
index 00000000..47566edc
Binary files /dev/null and b/trunk/server/src/uds/auths/RegexLdap/auth.png differ
diff --git a/trunk/server/src/uds/auths/Sample/SampleAuth.py b/trunk/server/src/uds/auths/Sample/SampleAuth.py
new file mode 100644
index 00000000..b5158a58
--- /dev/null
+++ b/trunk/server/src/uds/auths/Sample/SampleAuth.py
@@ -0,0 +1,307 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as translatable
+from uds.core.ui.UserInterface import gui
+from uds.core import auths
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleAuth(auths.Authenticator):
+ '''
+ This class represents a sample authenticator.
+
+ As this, it will provide:
+ * The authenticator functionality
+ * 3 Groups, "Mortals", "Gods" and "Daemons", just random group names selected.. :-),
+ plus groups that we enter at Authenticator form, from admin interface.
+ * Search of groups (inside the 3 groups used in this sample plus entered)
+ * Search for people (will return the search string + 000...999 as usernames)
+ * The Required form description for administration interface, so admins can create
+ new authenticators of this kind.
+
+ In this sample, we will provide a simple standard auth, with owner drawn
+ login form that will simply show users that has been created and allow web user
+ to select one of them.
+
+ For this class to get visible at administration client as a authenticator type,
+ we MUST register it at package __init__
+
+ :note: At class level, the translations must be simply marked as so
+ using ugettext_noop. This is done in this way because we will translate
+ the string when it is sent to the administration client.
+ '''
+
+ #: Name of type, used at administration interface to identify this
+ #: authenticator (i.e. LDAP, SAML, ...)
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeName = translatable('Sample Authenticator')
+
+ #: Name of type used by Managers to identify this type of service
+ #: We could have used here the Class name, but we decided that the
+ #: module implementator will be the one that will provide a name that
+ #: will relation the class (type) and that name.
+ typeType = 'SampleAuthenticator'
+
+ #: Description shown at administration level for this authenticator.
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeDescription = translatable('Sample dummy authenticator')
+
+
+ #: Icon file, used to represent this authenticator at administration interface
+ #: This file should be at same folder as this class is, except if you provide
+ #: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
+ iconFile = 'auth.png'
+
+ #: Mark this authenticator as that the users comes from outside the UDS
+ #: database, that are most authenticator (except Internal DB)
+ #: True is the default value, so we do not need it in fact
+ # isExternalSource = True
+
+ #: If we need to enter the password for this user when creating a new
+ #: user at administration interface. Used basically by internal authenticator.
+ #: False is the default value, so this is not needed in fact
+ #: needsPassword = False
+
+ #: Label for username field, shown at administration interface user form.
+ userNameLabel = translatable('Fake User')
+
+ # Label for group field, shown at administration interface user form.
+ groupNameLabel = translatable('Fake Group')
+
+ #: Definition of this type of authenticator form
+ #: We will define a simple form where we will use a simple
+ #: list editor to allow entering a few group names
+
+ groups = gui.EditableList(label=translatable('Groups'), values = ['Gods', 'Daemons', 'Mortals'])
+
+ def initialize(self, values):
+ '''
+ Simply check if we have
+ at least one group in the list
+ '''
+
+ # To avoid problems, we only check data if values are passed
+ # If values are not passed in, form data will only be available after
+ # unserialization, and at this point all will be default values
+ # so self.groups.value will be []
+ if values is not None and len(self.groups.value) < 2:
+ raise auths.Authenticator.ValidationException(translatable('We need more that two items!'))
+
+ def searchUsers(self, pattern):
+ '''
+ Here we will receive a pattern for searching users.
+
+ This method is invoked from interface, so an administrator can search users.
+
+ If we do not provide this method, the authenticator will not provide search
+ facility for users. In our case, we will simply return a list of users
+ (array of dictionaries with ids and names) with the pattern plus 1..10
+ '''
+ return [ { 'id' : '{0}-{1}'.format(pattern, a), 'name' : '{0} number {1}'.format(pattern, a) } for a in range(1, 10)]
+
+ def searchGroups(self, pattern):
+ '''
+ Here we we will receive a patter for searching groups.
+
+ In this sample, we will try to locate elements that where entered at
+ sample authenticator form (when created), and return the ones that
+ contains the pattern indicated.
+ '''
+ pattern = pattern.lower()
+ res = []
+ for g in self.groups.value:
+ if g.lower().find(pattern) != -1:
+ res.append({'id' : g, 'name' : ''})
+ return res
+
+ def authenticate(self, username, credentials, groupsManager):
+ '''
+ This method is invoked by UDS whenever it needs an user to be authenticated.
+ It is used from web interface, but also from administration interface to
+ check credentials and access of user.
+
+ The tricky part of this method is the groupsManager, but it's easy to
+ understand what is used it for.
+
+ Imagine some authenticator, for example, an LDAP. It has its users, it has
+ its groups, and it has it relations (which user belongs to which group).
+
+ Now think about UDS. UDS know nothing about this, it only knows what
+ the administator has entered at admin interface (groups mainly, but he can
+ create users also).
+
+ UDS knows about this groups, but we need to relation those with the ones
+ know by the authenticator.
+
+ To do this, we have created a simple mechanism, where the authenticator
+ receives a groupsManager, that knows all groups known by UDS, and has
+ the method so the authenticator can say, for the username being validated,
+ to which uds groups it belongs to.
+
+ This is done using the :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
+ method of the provided groups manager.
+
+ At return, UDS will do two things:
+ * If there is no group inside the groupsManager mareked as valid, it will
+ denied access.
+ * If there is some groups marked as valid, it will refresh the known
+ UDS relations (this means that the database will be refresehd so the user
+ has valid groups).
+
+ This also means that the group membership is only checked at user login (well,
+ in fact its also checked when an administrator tries to modify an user)
+
+ So, authenticate must not also validate the user credentials, but also
+ indicate the group membership of this user inside UDS.
+
+ :note: groupsManager is an in/out parameter
+ '''
+ if username != credentials: # All users with same username and password are allowed
+ return False
+
+ # Now the tricky part. We will make this user belong to groups that contains at leat
+ # two letters equals to the groups names known by UDS
+ # For this, we will ask the groups manager for the groups names, and will check that and,
+ # if the user match this criteria, will mark that group as valid
+ for g in groupsManager.getGroupsNames():
+ if len(set(g.lower()).intersection(username.lower())) >= 2:
+ groupsManager.validate(g)
+
+ return True
+
+ def getGroups(self, username, groupsManager):
+ '''
+ As with authenticator part related to groupsManager, this
+ method will fill the groups to which the specified username belongs to.
+
+ We have to fill up groupsManager from two different places, so it's not
+ a bad idea to make a method that get the "real" authenticator groups and
+ them simply call to :py:meth:uds.core.auths.GroupsManager.GroupsManager.validate
+
+ In our case, we simply repeat the process that we also do at authenticate
+ '''
+ for g in groupsManager.getGroupsNames():
+ if len(set(g.lower()).intersection(username.lower())) >= 2:
+ groupsManager.validate(g)
+
+ def getHtml(self, request):
+ '''
+ If we override this method from the base one, we are telling UDS
+ that we want to draw our own authenticator.
+
+ This way, we can do whataver we want here (for example redirect to a site
+ for a single sign on) generation our ouwn html (and javascript ofc).
+
+ '''
+ # Here there is a sample, commented out
+ # In this sample, we will make a list of valid users, and when clicked,
+ # it will fill up original form with username and same password, and submit it.
+ #res = ''
+ #for u in self.dbAuthenticator().users.all():
+ # res += '{0}
'.format(u.name)
+ #
+ #res += ''
+ #return res
+
+ # I know, this is a bit ugly, but this is just a sample :-)
+
+ res = 'Login name:
'
+ res +='Login
'
+ return res
+
+
+ def authCallback(self, parameters, gm):
+ '''
+ We provide this as a sample of callback for an user.
+ We will accept all petitions that has "user" parameter
+
+ This method will get invoked by url redirections, probably by an SSO.
+
+ The idea behind this is that we can provide:
+ * Simple user/password authentications
+ * Own authentications (not UDS, authenticator "owned"), but with no redirections
+ * Own authentications via redirections (as most SSO will do)
+
+ Here, we will receive the parameters for this
+ '''
+ user = parameters.get('user', None)
+
+ return user
+
+ def createUser(self, usrData):
+ '''
+ This method provides a "check oportunity" to authenticators for users created
+ manually at administration interface.
+
+ If we do not provide this method, the administration interface will not allow
+ to create new users "by hand", i mean, the "new" options from menus will dissapear.
+
+ usrData is a dictionary that contains the input parameters from user,
+ with at least name, realName, comments, state & password.
+
+ We can modify this parameters, we can modify ALL, but name is not recommended to
+ modify it unles you know what you are doing.
+
+ Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
+ '''
+ from uds.core.util.State import State
+ usrData['realName'] = usrData['name'] + ' ' + usrData['name']
+ usrData['state'] = State.INACTIVE
+
+ def modifyUser(self, usrData):
+ '''
+ This method provides a "check opportunity" to authenticator for users modified
+ at administration interface.
+
+ If we do not provide this method, nothing will happen (default one does nothing, but
+ it's valid).
+
+ usrData is a dictionary that contains the input parameters from user,
+ with at least name, realName, comments, state & password.
+
+ We can modify this parameters, we can modify ALL, but name is not recommended to
+ modify it unless you know what you are doing.
+
+ Here, we will simply update the realName of the user, and (we have to take care
+ this this kind of things) modify the userName to a new one, the original plus '-1'
+ '''
+ usrData['realName'] = usrData['name'] + ' ' + usrData['name']
+ usrData['name'] = usrData['name'] + '-1'
diff --git a/trunk/server/src/uds/auths/Sample/__init__.py b/trunk/server/src/uds/auths/Sample/__init__.py
new file mode 100644
index 00000000..7ed261f4
--- /dev/null
+++ b/trunk/server/src/uds/auths/Sample/__init__.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+Sample authenticator. We import here the module, and uds.auths module will
+take care of registering it as provider
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from SampleAuth import SampleAuth
+
diff --git a/trunk/server/src/uds/auths/Sample/auth.png b/trunk/server/src/uds/auths/Sample/auth.png
new file mode 100644
index 00000000..c8560b1d
Binary files /dev/null and b/trunk/server/src/uds/auths/Sample/auth.png differ
diff --git a/trunk/server/src/uds/auths/SimpleLDAP/Authenticator.py b/trunk/server/src/uds/auths/SimpleLDAP/Authenticator.py
new file mode 100644
index 00000000..97e36689
--- /dev/null
+++ b/trunk/server/src/uds/auths/SimpleLDAP/Authenticator.py
@@ -0,0 +1,424 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core.auths import Authenticator
+import ldap
+
+import logging
+from uds.core.auths.Exceptions import AuthenticatorException
+
+logger = logging.getLogger(__name__)
+
+LDAP_RESULT_LIMIT = 50
+
+class SimpleLDAPAuthenticator(Authenticator):
+
+ host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('VMWare VC Server IP or Hostname'), required = True)
+ port = gui.NumericField(length=5, label = _('Port'), defvalue = '389', order = 2, tooltip = _('Ldap port (389 for non ssl, 636 for ssl normally'), required = True)
+ ssl = gui.CheckBoxField(label = _('Use SSL'), order = 3, tooltip = _('If checked, will use a ssl connection to ldap (if port is 389, will use in fact port 636)'))
+ username = gui.TextField(length=64, label = _('Ldap User'), order = 4, tooltip = _('Username with read privileges on the base selected'), required = True)
+ password = gui.PasswordField(lenth=32, label = _('Password'), order = 5, tooltip = _('Password of the ldap user'), required = True)
+ timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 6, tooltip = _('Timeout in seconds of connection to LDAP'), required = True)
+ ldapBase = gui.TextField(length=64, label = _('Base'), order = 7, tooltip = _('Common search base (used for "users" and "groups"'), required = True)
+ userClass = gui.TextField(length=64, label = _('User class'), defvalue = 'posixAccount', order = 8, tooltip = _('Class for LDAP users (normally posixAccount)'), required = True)
+ userIdAttr = gui.TextField(length=64, label = _('User Id Attr'), defvalue = 'uid', order = 9, tooltip = _('Attribute that contains the user id'), required = True)
+ userNameAttr = gui.TextField(length=64, label = _('User Name Attr'), defvalue = 'uid', order = 10, tooltip = _('Attributes that contains the user name (list of comma separated values)'), required = True)
+ groupClass = gui.TextField(length=64, label = _('Group class'), defvalue = 'posixGroup', order = 11, tooltip = _('Class for LDAP groups (normally poxisGroup)'), required = True)
+ groupIdAttr = gui.TextField(length=64, label = _('Group Id Attr'), defvalue = 'cn', order = 12, tooltip = _('Attribute that contains the group id'), required = True)
+ memberAttr = gui.TextField(length=64, label = _('Group membership attr'), defvalue = 'memberUid', order = 13, tooltip = _('Attribute of the group that contains the users belonging to it'), required = True)
+
+ typeName = _('SimpleLDAP Authenticator')
+ typeType = 'SimpleLdapAuthenticator'
+ typeDescription = _('Simple LDAP authenticator')
+ iconFile = 'auth.png'
+
+ # If it has and external source where to get "new" users (groups must be declared inside UDS)
+ isExternalSource = True
+ # If we need to enter the password for this user
+ needsPassword = False
+ # Label for username field
+ userNameLabel = _('Username')
+ # Label for group field
+ groupNameLabel = _("Group")
+ # Label for password field
+ passwordLabel = _("Password")
+
+ def __init__(self, dbAuth, environment, values = None):
+ super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values)
+ if values != None:
+ self._host = values['host']
+ self._port = values['port']
+ self._ssl = gui.strToBool(values['ssl'])
+ self._username = values['username']
+ self._password = values['password']
+ self._timeout = values['timeout']
+ self._ldapBase = values['ldapBase']
+ self._userClass = values['userClass']
+ self._groupClass = values['groupClass']
+ self._userIdAttr = values['userIdAttr']
+ self._groupIdAttr = values['groupIdAttr']
+ self._memberAttr = values['memberAttr']
+ self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
+ else:
+ self._host = None
+ self._port = None
+ self._ssl = None
+ self._username = None
+ self._password = None
+ self._timeout = None
+ self._ldapBase = None
+ self._userClass = None
+ self._groupClass = None
+ self._userIdAttr = None
+ self._groupIdAttr = None
+ self._memberAttr = None
+ self._userNameAttr = None
+ self._connection = None
+
+ def valuesDict(self):
+ return { 'host' : self._host, 'port' : self._port, 'ssl' : gui.boolToStr(self._ssl),
+ 'username' : self._username, 'password' : self._password, 'timeout' : self._timeout,
+ 'ldapBase' : self._ldapBase, 'userClass' : self._userClass, 'groupClass' : self._groupClass,
+ 'userIdAttr' : self._userIdAttr, 'groupIdAttr' : self._groupIdAttr, 'memberAttr' : self._memberAttr,
+ 'userNameAttr' : self._userNameAttr
+ }
+
+ def __str__(self):
+ return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format(
+ self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr,
+ self._userNameAttr)
+
+ def marshal(self):
+ return str.join('\t', ['v1',
+ self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
+ self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr ])
+
+ def unmarshal(self, str):
+ data = str.split('\t')
+ if data[0] == 'v1':
+ logger.debug("Data: {0}".format(data[1:]))
+ self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[1:]
+ self._ssl = gui.strToBool(self._ssl)
+
+ def __connection(self, username = None, password = None):
+ if self._connection is None or username is not None: # We want this method also to check credentials
+ l = None
+ cache = False
+ try:
+ #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9)
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
+ schema = self._ssl and 'ldaps' or 'ldap'
+ port = self._port != '389' and ':' + self._port or ''
+ uri = "%s://%s%s" % (schema, self._host, port)
+ logger.debug('Ldap uri: {0}'.format(uri))
+ l = ldap.initialize(uri=uri)
+ l.network_timeout = l.timeout = int(self._timeout)
+ l.protocol_version = ldap.VERSION3
+
+ if username is None:
+ cache = True
+ username = self._username
+ password = self._password
+
+ l.simple_bind_s(who = username, cred = password)
+ except ldap.LDAPError, e:
+ str = _('Ldap connection error: ')
+ if type(e.message) == dict:
+ str += e.message.has_key('info') and e.message['info'] + ',' or ''
+ str += e.message.has_key('desc') and e.message['desc'] or ''
+ else :
+ str += str(e)
+ raise Exception(str)
+ if cache is True:
+ self._connection = l
+ else:
+ return l # Do not cache nor overwrite "global" connection
+ return self._connection
+
+ def __getUser(self, username):
+ try:
+ con = self.__connection()
+ filter = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, username)
+ attrlist = self._userNameAttr.split(',') + [self._userIdAttr]
+ logger.debug('Getuser filter: {0}, attr list: {1}'.format(filter, attrlist))
+ res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
+ filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
+ usr = dict(( k, '' ) for k in attrlist)
+ usr.update(res[1])
+ usr.update( {'dn' : res[0], '_id' : username })
+ logger.debug('Usr: {0}'.format(usr))
+ return usr
+ except Exception, e:
+ logger.exception('Exception:')
+ return None
+
+ def __getGroup(self, groupName):
+ try:
+ con = self.__connection()
+ filter = '(&(objectClass=%s)(%s=%s))' % (self._groupClass, self._groupIdAttr, groupName)
+ attrlist = [self._memberAttr]
+ logger.debug('Getgroup filter: {0}, attr list {1}'.format(filter, attrlist))
+ res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE,
+ filterstr = filter, attrlist = attrlist, sizelimit = LDAP_RESULT_LIMIT)[0]
+ grp = dict(( k, [''] ) for k in attrlist)
+ grp.update(res[1])
+ grp.update( {'dn' : res[0], '_id' : groupName })
+ logger.debug('Group: {0}'.format(grp))
+ return grp
+ except Exception, e:
+ logger.exception('Exception:')
+ return None
+
+
+ def __getGroups(self, usr):
+ try:
+ con = self.__connection()
+ filter = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn'])
+ logger.debug('Filter: {0}'.format(filter))
+ res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = filter, attrlist = [self._groupIdAttr],
+ sizelimit = LDAP_RESULT_LIMIT)
+ groups = {}
+ for g in res:
+ v = g[1][self._groupIdAttr]
+ if type(v) is not list:
+ v = [v]
+ for gg in v:
+ groups[str(gg)] = g[0]
+ logger.debug('Groups: {0}'.format(groups))
+ return groups
+
+ except Exception:
+ return {}
+
+
+ def __getUserRealName(self, usr):
+ '''
+ Tries to extract the real name for this user. Will return all atttributes (joint)
+ specified in _userNameAttr (comma separated).
+ '''
+ return ' '.join([ (type(usr.get(id_, '')) is list and ' '.join(( str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',') ]).strip()
+
+ def authenticate(self, username, credentials, groupsManager):
+ '''
+ Must authenticate the user.
+ We can have to different situations here:
+ 1.- The authenticator is external source, what means that users may be unknown to system before callig this
+ 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call
+ We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager.
+ The group manager is responsible for letting know the authenticator which groups we currently has active.
+ @see: uds.core.auths.GroupsManager
+ '''
+ try:
+ # Locate the user at LDAP
+ usr = self.__getUser(username)
+
+ if usr is None:
+ return False
+
+ # Let's see first if it credentials are fine
+ self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect
+
+ groupsManager.validate(self.__getGroups(usr).keys())
+
+ return True
+
+ except Exception:
+ return False
+
+ def createUser(self, usrData):
+ '''
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
+ @return: Raises an exception (AuthException) it things didn't went fine
+ '''
+ res = self.__getUser(usrData['name'])
+ if res is None:
+ raise AuthenticatorException(_('Username not found'))
+ # Fills back realName field
+ usrData['realName'] = self.__getUserRealName(res)
+
+ def getRealName(self, username):
+ '''
+ Tries to get the real name of an user
+ '''
+ res = self.__getUser(username)
+ if res is None:
+ return username
+ return self.__getUserRealName(res)
+
+ def modifyUser(self, usrData):
+ '''
+ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
+ Modify user has no reason on external sources, so it will never be used (probably)
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password
+ @return: Raises an exception it things don't goes fine
+ '''
+ return self.createUser(usrData)
+
+ def createGroup(self, groupData):
+ '''
+ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..)
+ External sources already has its own groups and, at most, it can check if it exists on external source before accepting it
+ Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to
+ @params groupData: a dict that has, at least, name, comments and active
+ @return: Raises an exception it things don't goes fine
+ '''
+ res = self.__getGroup(groupData['name'])
+ if res is None:
+ raise AuthenticatorException(_('Group not found'))
+
+
+ def getGroups(self, username, groupsManager):
+ '''
+ Looks for the real groups to which the specified user belongs
+ Updates groups manager with valid groups
+ Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used)
+ '''
+ user = self.__getUser(username)
+ if user is None:
+ raise AuthenticatorException(_('Username not found'))
+ groupsManager.validate(self.__getGroups(user).keys())
+
+ def searchUsers(self, pattern):
+ try:
+ con = self.__connection()
+ res = []
+ for r in con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
+ usrId = r[1].get(self._userIdAttr, '')
+ usrId = type(usrId) == list and usrId[0] or usrId
+ res.append( { 'id' : usrId,
+ 'name' : self.__getUserRealName(r[1]) } )
+ return res
+ except Exception, e:
+ logger.exception("Exception: ")
+ raise AuthenticatorException(_('Too many results, be more specific'))
+
+ def searchGroups(self, pattern):
+ try:
+ con = self.__connection()
+ res = []
+ for r in con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=%s*))' % (self._groupClass, self._groupIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT):
+ grpId = r[1].get(self._groupIdAttr, '')
+ grpId = type(grpId) == list and grpId[0] or grpId
+ res.append( { 'id' : grpId,
+ 'name' : grpId } )
+ return res
+ except Exception, e:
+ logger.exception("Exception: ")
+ raise AuthenticatorException(_('Too many results, be more specific'))
+
+
+ @staticmethod
+ def test(env, data):
+ try:
+ auth = SimpleLDAPAuthenticator(None, env, data)
+ return auth.testConnection()
+ except Exception, e:
+ logger.error("Exception found testing Simple LDAP auth {0}: {1}".format(e.__class__, e))
+ return [False, "Error testing connection"]
+
+ def testConnection(self):
+ try:
+ con = self.__connection()
+ except Exception, e:
+ return [False, str(e)]
+
+ try:
+ con.search_s(base = self._ldapBase, scope = ldap.SCOPE_BASE)
+ except Exception:
+ return [False, _('Ldap search base is incorrect')]
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._userClass, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user class seems to be incorrect (no user found by that class)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap group class seems to be incorrect (no group found by that class)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._userIdAttr, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(%s=*)' % self._groupIdAttr, sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ # Now test objectclass and attribute of users
+ try:
+ if len(con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1:
+ raise Exception()
+ return [False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)')]
+ except Exception, e:
+ # If found 1 or more, all right
+ pass
+
+ # And group part, with membership
+ try:
+ res = con.search_ext_s(base = self._ldapBase, scope = ldap.SCOPE_SUBTREE, filterstr = '(&(objectClass=%s)(%s=*))' % (self._groupClass, self._groupIdAttr), attrlist = [self._memberAttr])
+ if len(res) == 0:
+ raise Exception(_('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)'))
+ ok = False
+ for r in res:
+ if r[1].has_key(self._memberAttr) is True:
+ ok = True
+ break
+ if ok is False:
+ raise Exception(_('Can\'t locate any group with the membership attribute specified'))
+ except Exception, e:
+ return [False, str(e)]
+
+
+
+ return [True, _("Connection params seem correct, test was succesfully executed")]
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/auths/SimpleLDAP/__init__.py b/trunk/server/src/uds/auths/SimpleLDAP/__init__.py
new file mode 100644
index 00000000..88d874be
--- /dev/null
+++ b/trunk/server/src/uds/auths/SimpleLDAP/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+'''
+
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from Authenticator import SimpleLDAPAuthenticator
+
diff --git a/trunk/server/src/uds/auths/SimpleLDAP/auth.png b/trunk/server/src/uds/auths/SimpleLDAP/auth.png
new file mode 100644
index 00000000..47566edc
Binary files /dev/null and b/trunk/server/src/uds/auths/SimpleLDAP/auth.png differ
diff --git a/trunk/server/src/uds/auths/__init__.py b/trunk/server/src/uds/auths/__init__.py
new file mode 100644
index 00000000..be007c22
--- /dev/null
+++ b/trunk/server/src/uds/auths/__init__.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Authentication modules for uds are contained inside this module.
+To create a new authentication module, you will need to follow this steps:
+ 1.- Create the authentication module, probably based on an existing one
+ 2.- Insert the module as child of this module
+ 3.- Import the class of your authentication module at __init__. For example::
+ from Authenticator import SimpleAthenticator
+ 4.- Done. At Server restart, the module will be recognized, loaded and treated
+
+The registration of modules is done locating subclases of :py:class:`uds.core.auths.Authentication`
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+def __init__():
+ '''
+ This imports all packages that are descendant of this package, and, after that,
+ it register all subclases of authenticator as
+ '''
+ import os.path, pkgutil
+ import sys
+ from uds.core import auths
+
+ # Dinamycally import children of this package. The __init__.py files must register, if needed, inside AuthsFactory
+ pkgpath = os.path.dirname(sys.modules[__name__].__file__)
+ for _, name, _ in pkgutil.iter_modules([pkgpath]):
+ __import__(name, globals(), locals(), [], -1)
+
+ a = auths.Authenticator
+ for cls in a.__subclasses__():
+ auths.factory().insert(cls)
+
+__init__()
diff --git a/trunk/server/src/uds/core/BaseModule.py b/trunk/server/src/uds/core/BaseModule.py
new file mode 100644
index 00000000..33305cb9
--- /dev/null
+++ b/trunk/server/src/uds/core/BaseModule.py
@@ -0,0 +1,266 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.ui.UserInterface import UserInterface
+from uds.core import Environmentable
+from uds.core import Serializable
+import base64, os.path, sys, logging
+
+logger = logging.getLogger(__name__)
+
+class Module(UserInterface, Environmentable, Serializable):
+ '''
+ Base class for all modules used by UDS.
+ This base module provides all the needed methods that modules must implement
+
+ All modules must, at least, implement the following:
+
+ * Attributes:
+ * :py:attr:`.typeName`:
+ Name for this type of module (human readable) to assign to the module (string)
+ This name will be used to let the administrator identify this module.
+ * :py:attr:`.typeType`:
+ Name for this type of module (machine only) to assing to the module (string)
+ This name will be used internally to identify when a serialized module corresponds with this class.
+ * :py:attr:`.typeDescription`:
+ Description for this type of module.
+ This descriptio will be used to let the administrator identify what this module provides
+ * :py:attr:`.iconFile`: This is an icon file, in png format, used at administration client to identify this module.
+ This parameter may be optionall if you override the "icon" method.
+ * Own Methods:
+ * :py:meth:`.__init__`
+ The default constructor. The environment value is always provided (see Environment), but the
+ default values provided can be None.
+ Remember to allow the instantiation of the module with default params, because when deserialization is done,
+ the process is first instatiate with an environment but no parameters and then call "unmarshal" from Serializable.
+ * :py:meth:`.test`
+ * :py:meth:`.check`
+ * :py:meth:`.destroy`: Optional
+ * :py:meth:`.icon`: Optional, if you provide an icon file, this method loads it from module folder,
+ but you can override this so the icon is obtained from other source.
+ * :py:meth:`.marshal`
+ By default, this method serializes the values provided by user in form fields. You can override it,
+ but now it's not needed because you can access config vars using Form Fields.
+
+ Anyway, if you override this method, you must also override next one
+ * :py:meth:`.unmarshal`
+ By default, this method de-serializes the values provided by user in form fields. You can override it,
+ but now it's not needed because you can access config vars using Form Fields.
+
+ Anyway, if you override this method, you must also override previous one
+
+ * UserInterface Methods:
+ * :py:meth:`uds.core.ui.UserInterface.UserInterface.valuesDict`
+ This method, by default, provides the values contained in the form fields. If you don't override the marshal and
+ unmarshal, this method should be fine as is for you also.
+
+
+ Environmentable is a base class that provides utility method to access a separate Environment for every single
+ module.
+ '''
+ #: Which coded to use to encode module by default.
+ #: This overrides the Environmentable and Serializable Attribute, but in all cases we are using 'base64'
+ CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
+
+ #: Basic name used to provide the administrator an "huma readable" form for the module
+ typeName = 'Base Module'
+ #: Internal type name, used by system to locate this module
+ typeType = 'BaseModule'
+ #: Description of this module, used at admin level
+ typeDescription = 'Base Module'
+ #: Icon file, relative to module folders
+ iconFile = 'base.png' # This is expected to be png, use this format always
+
+ class ValidationException(Exception):
+ '''
+ Exception used to indicate that the params assigned are invalid
+ '''
+
+ @classmethod
+ def name(cls):
+ '''
+ Returns "translated" typeName, using ugettext for transforming
+ cls.typeName
+
+ Args:
+ cls: This is a class method, so cls is the class
+
+ Returns:
+ Translated type name (using ugettext)
+ '''
+ return _(cls.typeName)
+
+ @classmethod
+ def type(cls):
+ '''
+ Returns typeType
+
+ Args:
+ cls: This is a class method, so cls is the class
+
+ Returns:
+ the typeType of this class (or derived class)
+ '''
+ return cls.typeType
+
+ @classmethod
+ def description(cls):
+ '''
+ This method returns the "translated" description, that is, using
+ ugettext for transforming cls.typeDescription.
+
+ Args:
+ cls: This is a class method, so cls is the class
+
+ Returns:
+ Translated description (using ugettext)
+
+ '''
+ return _(cls.typeDescription)
+
+
+ @classmethod
+ def icon(cls, inBase64 = True):
+ '''
+ Reads the file specified by iconFile at module folder, and returns it content.
+ This is used to obtain an icon so administration can represent it.
+
+ Args:
+ cls: Class
+
+ inBase64: If true, the image will be returned as base 64 encoded
+
+ Returns:
+ Base 64 encoded or raw image, obtained from the specified file at
+ 'iconFile' class attribute
+ '''
+ logger.debug('Loading icon for class {0} ({1})'.format(cls, cls.iconFile))
+ file_ = open( os.path.dirname(sys.modules[cls.__module__].__file__) + '/' + cls.iconFile, 'rb')
+ data = file_.read()
+ file_.close()
+ if inBase64 == True:
+ return base64.encodestring(data)
+ else:
+ return data
+
+ @staticmethod
+ def test(env, data):
+ '''
+ Test if the connection data is ok.
+
+ Returns an array, first value indicates "Ok" if true, "Bad" or "Error"
+ if false. Second is a string describing operation
+
+ Args:
+ env: environment passed for testing (temporal environment passed)
+
+ data: data passed for testing (data obtained from the form
+ definition)
+
+ Returns:
+ Array of two elements, first is True of False, depending on test
+ (True is all right, false is error),
+ second is an String with error, preferably internacionalizated..
+ '''
+ return [True, _("No connection checking method is implemented.")]
+
+ def __init__(self, environment, values = None):
+ '''
+ Do not forget to invoke this in your derived class using
+ "super(self.__class__, self).__init__(environment, values)".
+
+ We want to use the env, cache and storage methods outside class.
+ If not called, you must implement your own methods.
+
+ cache and storage are "convenient" methods to access _env.cache() and
+ _env.storage()
+
+ The values param is passed directly to UserInterface base.
+
+ The environment param is passed directly to environment.
+
+ Values are passed to __initialize__ method. It this is not None,
+ the values contains a dictionary of values received from administration gui,
+ that contains the form data requested from user.
+
+ If you override marshal, unmarshal and inherited UserInterface method
+ valuesDict, you must also take account of values (dict) provided at the
+ __init__ method of your class.
+ '''
+ UserInterface.__init__(self, values)
+ Environmentable.__init__(self, environment)
+ Serializable.__init__(self)
+
+ def __str__(self):
+ return "Base Module"
+
+ def marshal(self):
+ '''
+ By default and if not overriden by descendants, this method, overridden
+ from Serializable, and returns the serialization of
+ form field stored values.
+ '''
+ return self.serializeForm()
+
+ def unmarshal(self, str_):
+ '''
+ By default and if not overriden by descendants, this method recovers
+ data serialized using serializeForm
+ '''
+ self.unserializeForm(str_)
+
+ def check(self):
+ '''
+ Method that will provide the "check" capability for the module.
+
+ The return value that this method must provide is simply an string,
+ preferable internacionalizated.
+
+ Returns:
+ Internacionalized (using ugettext) string of result of the check.
+ '''
+ return _("No check method provided.")
+
+ def destroy(self):
+ '''
+ Invoked before deleting an module from database.
+
+ Do whatever needed here, as deleting associated data if needed
+ (no example come to my head right now... :-) )
+
+ Returns:
+ Nothing
+ '''
+ pass
+
diff --git a/trunk/server/src/uds/core/Environment.py b/trunk/server/src/uds/core/Environment.py
new file mode 100644
index 00000000..b0d2f189
--- /dev/null
+++ b/trunk/server/src/uds/core/Environment.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+TEMP_ENV = 'temporary'
+GLOBAL_ENV = 'global'
+
+class Environment(object):
+ '''
+ Class to manipulate the associated environment with "environmentable" classes (mainly modules).
+ It purpose is to provide an "object owned" environment, so every db record can contain associated values
+ not stored with main module data.
+ The environment is composed of a "cache" and a "storage". First are volatile data, while second are persistent data.
+ '''
+
+ def __init__(self, uniqueKey, idGenerators = {}):
+ '''
+ Initialized the Environment for the specified id
+ @param uniqueId: Key for this environment
+ @param idGenerators: Hash of generators of ids for this environment. This "generators of ids" feature
+ is used basically at User Services to auto-create ids for macs or names, using
+ {'mac' : UniqueMacGenerator, 'name' : UniqueNameGenerator } as argument.
+ '''
+ from uds.core.util.Cache import Cache
+ from uds.core.util.Storage import Storage
+ self._key = uniqueKey
+ self._cache = Cache(uniqueKey)
+ self._storage = Storage(uniqueKey)
+ self._idGenerators = idGenerators
+
+ def cache(self):
+ '''
+ Method to acces the cache of the environment.
+ @return: a referente to a Cache instance
+ '''
+ return self._cache
+
+ def storage(self):
+ '''
+ Method to acces the cache of the environment.
+ @return: a referente to an Storage Instance
+ '''
+ return self._storage
+
+ def idGenerators(self, generatorId):
+ '''
+ The idea of generator of id is to obtain at some moment Ids with a proper generator.
+ If the environment do not contains generators of id, this method will return None.
+ The id generator feature is used by User Services to obtain different auto-id generators, as macs or names
+ @param generatorId: Id of the generator to obtain
+ @return: Generator for that id, or None if no generator for that id is found
+ '''
+ if self._idGenerators.has_key(generatorId):
+ return self._idGenerators[generatorId]
+ return None
+
+ def key(self):
+ '''
+ @return: the key used for this environment
+ '''
+ return self._key
+
+ def clearRelatedData(self):
+ '''
+ Removes all related information from database for this environment.
+ '''
+ from uds.core.util.Cache import Cache
+ from uds.core.util.Storage import Storage
+ Cache.delete(self._key)
+ Storage.delete(self._key)
+ for __, v in self._idGenerators.iteritems():
+ v.release()
+
+ @staticmethod
+ def getEnvForTableElement(tblName, id_, idGeneratorsTypes = {}):
+ '''
+ From a table name, and a id, tries to load the associated environment or creates a new
+ one if no environment exists at database. The table name and the id are used to obtain the key
+ for the environment, so each element at database can have its own environment.
+ @param tblName: Table name
+ @param id_: Id of the element (normally primary key of the record for which we want an environment)
+ @param idGeneratorsTypes: Associated Generators. Defaults to none
+ @return: Obtained associated environment (may be empty if none exists at database, but it will be valid)
+ '''
+ name = 't-' + tblName + '-' + str(id_)
+ idGenerators = {}
+ for k,v in idGeneratorsTypes.iteritems():
+ idGenerators[k] = v(name)
+ return Environment(name, idGenerators)
+
+ @staticmethod
+ def getEnvForType(type_):
+ '''
+ Obtains an environment associated with a type instead of a record
+ @param type_: Type
+ @return Associated Environment
+ '''
+ return Environment('type-'+str(type_))
+
+ @staticmethod
+ def getTempEnv():
+ '''
+ Provides a temporary environment needed in some calls (test provider, for example)
+ It will not make environment persistent
+ '''
+ return Environment(TEMP_ENV) # TODO: In fact, we should provide a "null" cache and a "null" storage, but for now this is right
+
+ @staticmethod
+ def getGlobalEnv():
+ '''
+ Provides global environment
+ '''
+ return Environment(GLOBAL_ENV) # This environment is a global environment for general utility.
+
+class Environmentable(object):
+ '''
+ This is a base class provided for all objects that have an environment associated. These are mainly modules
+ '''
+
+ def __init__(self, environment):
+ '''
+ Initialized the element
+
+ Args:
+ environment: Environment to associate with
+ '''
+ self._env = environment
+
+ def setEnv(self, environment):
+ '''
+ Assigns a new environment
+
+ Args:
+ environment: Environment to assign
+ '''
+ self._env = environment
+
+ def env(self):
+ '''
+ Utility method to access the envionment contained by this object
+
+ Returns:
+ Environmnet for the object
+ '''
+ return self._env
+
+ def cache(self):
+ '''
+ Utility method to access the cache of the environment containe by this object
+
+ Returns:
+ Cache for the object
+ '''
+ return self._env.cache()
+
+ def storage(self):
+ '''
+ Utility method to access the storage of the environment containe by this object
+
+ Returns:
+ Storage for the object
+ '''
+ return self._env.storage()
+
+ def idGenerators(self, generatorId):
+ '''
+ Utility method to access the id generator of the environment containe by this object
+
+ Args:
+ generatorId: Id of the generator to obtain
+
+ Returns:
+ Generator for the object and the id specified
+ '''
+ return self._env.idGenerators(generatorId)
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/Serializable.py b/trunk/server/src/uds/core/Serializable.py
new file mode 100644
index 00000000..d8c26624
--- /dev/null
+++ b/trunk/server/src/uds/core/Serializable.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+class Serializable(object):
+ '''
+ This class represents the interface that all serializable objects must provide.
+
+ Every single serializable class must implement marshall & unmarshall methods. Also, the class must allow
+ to be initialized without parameters, so we can:
+ - Initialize the object with default values
+ - Read values from seralized data
+ '''
+ # Codify codec constant
+ CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
+
+ def __init__(self):
+ pass
+
+ def marshal(self):
+ '''
+ This is the method that must be overriden in order to serialize an object.
+
+ The system will use in fact 'seralize' and 'deserialize' methods, but theese are
+ only suitable methods to "codify" serialized values
+
+ :note: This method must be overridden
+ '''
+ raise Exception('Base marshaler called!!!')
+
+ def unmarshal(self, str_):
+ '''
+ This is the method that must be overriden in order to unserialize an object.
+
+ The system will use in fact 'seralize' and 'deserialize' methods, but theese are
+ only convenients methods to "codify" serialized values.
+
+ Take into account that _str can be '' (empty string), but hopefully it will never be none.
+ In that case, initialize the object with default values
+
+ Args:
+ str _ : String readed from persistent storage to deseralilize
+
+ :note: This method must be overridden
+ '''
+ raise Exception('Base unmarshaler called!!!')
+
+ def serialize(self):
+ '''
+ Serializes and "obfuscates' the data.
+
+ The codec used to encode the string is obtained from the instance CODEC, so derived classes can
+ overwrite this attribute to set another codec
+ '''
+ return self.marshal().encode(self.CODEC)
+
+ def unserialize(self, str_):
+ '''
+ des-obfuscates the data and then de-serializes it via unmarshal method
+
+ The codec used to decode the string is obtained from the instance CODEC, so derived classes can
+ overwrite this attribute to set another codec
+ '''
+ return self.unmarshal(str_.decode(self.CODEC))
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/__init__.py b/trunk/server/src/uds/core/__init__.py
new file mode 100644
index 00000000..7447ff0d
--- /dev/null
+++ b/trunk/server/src/uds/core/__init__.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Core of UDS.
+This package contains all core-related code for UDS
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+# Core needs tasks manager to register scheduled jobs, so we ensure of that here
+from Environment import Environmentable
+from Serializable import Serializable
+from BaseModule import Module
+import services
+import auths
+import transports
+
+def __init__():
+ from managers.TaskManager import TaskManager
+ TaskManager.registerScheduledTasks()
+
+__init__()
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/auths/AuthsFactory.py b/trunk/server/src/uds/core/auths/AuthsFactory.py
new file mode 100644
index 00000000..b28f776e
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/AuthsFactory.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+class AuthsFactory(object):
+ '''
+ This class holds the register of all known authentication modules
+ inside UDS.
+
+ It provides a way to register and recover Authentication providers.
+ '''
+ _factory = None
+
+ def __init__(self):
+ self._auths = {}
+
+ @staticmethod
+ def factory():
+ '''
+ Returns the factory that keeps the register of authentication providers.
+ '''
+ if AuthsFactory._factory == None:
+ AuthsFactory._factory = AuthsFactory()
+ return AuthsFactory._factory
+
+ def providers(self):
+ '''
+ Returns the list of authentication providers already registered.
+ '''
+ return self._auths
+
+ def insert(self, type_):
+ '''
+ Registers a new authentication provider
+ '''
+ self._auths[type_.type()] = type_
+
+ def lookup(self, typeName):
+ '''
+ Tries to locate an authentication provider and by its name, and, if
+ not found, returns None
+ '''
+ return self._auths.get(typeName, None)
diff --git a/trunk/server/src/uds/core/auths/BaseAuthenticator.py b/trunk/server/src/uds/core/auths/BaseAuthenticator.py
new file mode 100644
index 00000000..2d44d2c4
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/BaseAuthenticator.py
@@ -0,0 +1,606 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Base module for all authenticators
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core import Module
+from django.utils.translation import ugettext_noop as translatable
+from GroupsManager import GroupsManager
+from Exceptions import InvalidUserException
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Authenticator(Module):
+ '''
+ This class represents the base interface to implement authenticators.
+
+ An authenticator is responsible for managing user and groups of a kind
+ inside UDS. As so, it must provide a number of method and mechanics to
+ allow UDS to manage users and groups using that kind of authenticator.
+
+ Some samples of authenticators are LDAP, Internal Database, SAML, CAS, ...
+
+ As always, if you override __init__, do not forget to invoke base __init__ as this::
+
+ super(self.__class__, self).__init__(self, dbAuth, environment, values)
+
+ This is a MUST, so internal structured gets filled correctly, so don't forget it!.
+
+ The preferred method of doing initialization is to provide the :py:meth:`.initialize`,
+ and do not override __init__ method. This (initialize) will be invoked after
+ all internal initialization.
+
+ There are basically two kind of authenticators, that are "Externals" and
+ "Internals".
+
+ Internal authenticators are those where and administrator has created manually
+ the user at admin interface. The users are not created from an external source,
+ so if an user do not exist at UDS database, it will not be valid.
+ In other words, if you have an authenticator where you must create users,
+ you can modify them, you must assign passwords manually, and group membership
+ also must be assigned manually, the authenticator is not an externalSource.
+
+ As you can notice, almost avery authenticator except internal db will be
+ external source, so, by default, attribute that indicates that is an external
+ source is set to True.
+
+
+ In fact, internal source authenticator is intended to allow UDS to identify
+ if the users come from internal DB (just the case of local authenticator),
+ or the users come from other sources. Also, this allos UDS to know when to
+ "update" group membership information for an user whenever it logs in.
+
+ External authenticator are in fact all authenticators except local database,
+ so we have defined isExternalSource as True by default, that will be most
+ cases.
+
+ :note: All attributes that are "translatable" here means that they will be
+ translated when provided to administration interface, so remember
+ to mark them in your own authenticators as "translatable" using
+ ugettext_noop. We have aliased it here to "translatable" so it's
+ easier to understand.
+ '''
+
+ #: Name of type, used at administration interface to identify this
+ #: authenticator (i.e. LDAP, SAML, ...)
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeName = translatable('Base Authenticator')
+
+ #: Name of type used by Managers to identify this type of service
+ #: We could have used here the Class name, but we decided that the
+ #: module implementator will be the one that will provide a name that
+ #: will relation the class (type) and that name.
+ typeType = 'BaseAuthenticator'
+
+ #: Description shown at administration level for this authenticator.
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeDescription = translatable('Base Authenticator')
+
+
+ #: Icon file, used to represent this authenticator at administration interface
+ #: This file should be at same folder as this class is, except if you provide
+ #: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
+ iconFile = 'auth.png'
+
+ #: Mark this authenticator as that the users comes from outside the UDS
+ #: database, that are most authenticator (except Internal DB)
+ #: So, isInternalSource means that "user is kept at database only"
+ isExternalSource = True
+
+ #: If we need to enter the password for this user when creating a new
+ #: user at administration interface. Used basically by internal authenticator.
+ needsPassword = False
+
+ #: Label for username field, shown at administration interface user form.
+ userNameLabel = translatable('User name')
+
+ #: Label for group field, shown at administration interface user form.
+ groupNameLabel = translatable('Group name')
+
+ #: Label for password field, , shown at administration interface user form.
+ #: Not needed for external authenticators (where credentials are stored with
+ #: an already existing user.
+ passwordLabel = translatable('Password')
+
+ from User import User
+ from Group import Group
+
+ #: The type of user provided, normally standard user will be enough.
+ #: This is here so if we need it in some case, we can write our own
+ #: user class
+ userType = User
+
+ #: The type of group provided, normally standard group will be enough
+ #: This is here so if we need it in some case, we can write our own
+ #: group class
+ groupType = Group
+
+ def __init__(self, dbAuth, environment, values):
+ '''
+ Instantiathes the authenticator.
+ @param dbAuth: Database object for the authenticator
+ @param environment: Environment for the authenticator
+ @param values: Values passed to element
+ '''
+ self._dbAuth = dbAuth
+ super(Authenticator, self).__init__(environment, values)
+ self.initialize(values)
+
+ def initialize(self, values):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base methods.
+ This will get invoked when all initialization stuff is done
+
+ Args:
+ Values: If values is not none, this object is being initialized
+ from administration interface, and not unmarshal will be done.
+ If it's None, this is initialized internally, and unmarshal will
+ be called after this.
+
+ Default implementation does nothing
+ '''
+ pass
+
+ def dbAuthenticator(self):
+ '''
+ Helper method to access the Authenticator database object
+ '''
+ return self._dbAuth
+
+ def recreateGroups(self, user):
+ '''
+ Helper method, not needed to be overriden.
+ It simply checks if the source is external and if so, recreates
+ the user groups for storing them at database.
+
+ user param is a database user object
+ '''
+ if self.isExternalSource == True:
+ groupsManager = GroupsManager(self._dbAuth)
+ self.getGroups(user.name, groupsManager)
+ user.groups = [ g.dbGroup() for g in groupsManager.getValidGroups()]
+
+ def callbackUrl(self):
+ '''
+ Helper method to return callback url for self (authenticator).
+
+ This method will allow us to know where to do redirection in case
+ we need to use callback for authentication
+ '''
+ from auth import authCallbackUrl
+ return authCallbackUrl(self.dbAuthenticator())
+
+ def infoUrl(self):
+ '''
+ Helper method to return info url for this authenticator
+ '''
+ from auth import authInfoUrl
+ return authInfoUrl(self.dbAuthenticator())
+
+ @classmethod
+ def isCustom(cls):
+ '''
+ Helper to query if a class is custom (implements getHtml method)
+ '''
+ return cls.getHtml != Authenticator.getHtml
+
+ @classmethod
+ def canCheckUserPassword(cls):
+ '''
+ Helper method to query if a class can do a login using credentials
+ '''
+ return cls.authenticate != Authenticator.authenticate
+
+ def searchUsers(self, pattern):
+ '''
+ If you provide this method, the user will be allowed to search users,
+ that is, the search button at administration interface, at user form,
+ will be enabled.
+
+ Returns an array of users that match the supplied pattern
+ If none found, returns empty array.
+
+ Must return is an array of dictionaries that must contains 'id' and 'name'
+ example: [ {'id': 'user1', 'name': 'Nombre 1'} ]
+
+ Args:
+ pattern: Pattern to search for (simple pattern, string)
+
+ Returns
+ a list of found users for the pattern specified
+ '''
+ return []
+
+ def searchGroups(self, pattern):
+ '''
+ Returns an array of groups that match the supplied pattern
+ If none found, returns empty array. Items returned are BaseGroups (or derived)
+ If you override this method, the admin interface will allow the use of
+ "search" at group form. If not overriden, the search will not be allowed.
+
+ Must return array of dictionaries that must contains 'id' and 'name'
+ example: [ {'id': 'user1', 'name': 'Nombre 1'} ]
+
+ Default implementation returns empty array, but is never used because if
+ not overriden, search of groups will not be allowed.
+ '''
+ return []
+
+ def authenticate(self, username, credentials, groupsManager):
+ '''
+ This method must be overriden, and is responsible for authenticating
+ users.
+
+ We can have to different situations here:
+
+ * The authenticator is external source, what means that users may
+ be unknown to system before callig this
+ * The authenticator isn't external source, what means that users have
+ been manually added to system and are known before this call.
+ This will only happen at Internal DB Authenticator.
+
+ We receive the username, the credentials used (normally password, but can
+ be a public key or something related to pk) and a group manager.
+
+ The group manager is responsible for letting know the authenticator which
+ groups we currently has active.
+
+ Args:
+ username: User name to authenticate
+ credentilas: Credentials for this user, (password, pki, or whatever needs to be used). (string)
+ groupManager: Group manager to modify with groups to which this users belongs to.
+
+ Returns:
+ True if authentication success, False if don't.
+
+ See uds.core.auths.GroupsManager
+
+ :note: This method must check not only that the user has valid credentials, but also
+ check the valid groups from groupsManager.
+ If this method returns false, of method getValidGroups of the groupsManager
+ passed into this method has no elements, the user will be considered invalid.
+ So remember to check validity of groups this user belongs to (inside the authenticator,
+ not inside UDS) using groupsManager.validate(group to which this users belongs to).
+
+ This is done in this way, because UDS has only a subset of groups for this user, and
+ we let the authenticator decide inside wich groups of UDS this users is included.
+ '''
+ return False
+
+ def internalAuthenticate(self,username, credentials, groupsManager):
+ '''
+ This method is provided so "plugins" (For example, a custom dispatcher), can test
+ the username/credentials in an alternative way.
+
+ For example, ip authenticator generates, inside the custom html, a 1 time password
+ that will be used to authenticate the ip. If we create a custom dispatcher and we want
+ to auth the user without the html part being displayed, we have a big problem.
+
+ Using this method, the authenticator has the oportunitiy to, (for example, in case of
+ IP auth), ignore "credentials"
+
+ Args:
+ username: User name to authenticate
+ credentilas: Credentials for this user, (password, pki, or whatever needs to be used). (string)
+ groupManager: Group manager to modify with groups to which this users belongs to.
+
+ Returns:
+ True if authentication success, False if don't.
+ By default, internalAuthenticate simply invokes authenticate, but this method
+ is here so you can provide your own method if needed
+
+ See uds.core.auths.GroupsManager
+
+ :note: This method must check not only that the user has valid credentials, but also
+ check the valid groups from groupsManager.
+ If this method returns false, of method getValidGroups of the groupsManager
+ passed into this method has no elements, the user will be considered invalid.
+ So remember to check validity of groups this user belongs to (inside the authenticator,
+ not inside UDS) using groupsManager.validate(group to which this users belongs to).
+
+ This is done in this way, because UDS has only a subset of groups for this user, and
+ we let the authenticator decide inside wich groups of UDS this users is included.
+ '''
+ return self.authenticate(username, credentials, groupsManager)
+
+ def logout(self, username):
+ '''
+ Invoked whenever an user logs out.
+
+ Notice that authenticators that provides getHtml method are considered "custom", and
+ these authenticators will never be used to allow an user to access administration interface
+ (they will be filtered out)
+
+ By default, this method does nothing.
+
+ Args:
+
+ username: Name of the user that logged out
+
+ Returns:
+
+ None if nothing has to be done by UDS. An URL (absolute or relative), if it has to redirect
+ the user to somewhere.
+
+ :note: This method will be invoked also for administration log out (it it's done), but return
+ result will be passed to administration interface, that will invoke the URL but nothing
+ will be shown to the user.
+ Also, notice that this method will only be invoked "implicity", this means that will be
+ invoked if user requests "log out", but maybe it will never be invoked.
+
+ '''
+ return None
+
+ def getForAuth(self, username):
+ '''
+ Process the username for this authenticator and returns it.
+ This transformation is used for transports only, not for transforming
+ anything at login time. Transports that will need the username, will invoke
+ this method.
+ For example, an authenticator can add '@domain' so transport use the complete
+ 'user@domain' instead of 'user'.
+
+ Right now, all authenticators keep this value "as is", i mean, it simply
+ returns the unprocessed username
+ '''
+ return username
+
+ def getGroups(self, username, groupsManager):
+ '''
+ Looks for the real groups to which the specified user belongs.
+
+ You MUST override this method, UDS will call it whenever it needs to refresh an user group membership.
+
+ The expected behavior of this method is to mark valid groups in the :py:class:`uds.core.auths.GroupsManager` provided, normally
+ calling its :py:meth:`uds.core.auths.GroupsManager.validate` method with groups names provided by the authenticator itself
+ (for example, LDAP, AD, ...)
+ '''
+ pass
+
+ def getHtml(self, request):
+ '''
+ If you override this method, and returns something different of None,
+ UDS will consider your authenticator as "Owner draw", that is, that it
+ will not use the standard form for user authentication.
+
+ Args:
+ Request is the DJango request received for generating this html,
+ with included user ip at request.ip.
+
+ We have here a few things that we should know for creating our own
+ html for authenticator:
+
+ * We use jQuery, so your javascript can use it
+ * The id of the username input field is **id_user**
+ * The id of the password input field is **id_password**
+ * The id of the login form is **loginform**
+ * The id of the "back to login" link is **backToLogin**
+
+ This is what happens when an authenticator that has getHtml method is
+ selected in the front end (from the combo shown):
+
+ * The div with id **login** is hidden.
+ * The div with id **nonStandard** is shown
+ * Using Ajax, the html provided by this method is requested for
+ the authenticator
+ * The returned html is rendered inside **nonStandardLogin** div.
+ * The **nonStandard** div is shown.
+
+ **nonStandard** div has two inner divs, **nonStandardLogin** and
+ **divBackToLogin**. If there is no standard auths, divBackToLogin is
+ erased.
+
+ With this, and :py:meth:.authCallback method, we can add SSO engines
+ to UDS with no much problems.
+ '''
+ return None
+
+ def authCallback(self, parameters, gm):
+ '''
+ There is a view inside UDS, an url, that will redirect the petition
+ to this callback.
+
+ If someone gets authenticated via this callback, the method will return
+ an "username" must be return. This username will be used to:
+
+ * Add user to UDS
+ * Get user groups.
+
+ So, if this callback is called, also get the membership to groups of the user, and keep them.
+ This method will have to keep track of those until UDS request that groups
+ using getGroups. (This is easy, using storage() provided with the environment (env())
+
+ If this returns None, or empty, the authentication will be considered "invalid"
+ and an error will be shown.
+
+ Args:
+ parameters: all GET and POST received parameters
+ gm: Groups manager, you MUST check group membership using this gm
+
+ Return:
+ An username if validation check is successfull, None if not
+
+ You can also return an exception here and, if you don't wont to check the user login,
+ you can raise :py:class:uds.core.auths.Exceptions.Redirect to redirect user to somewhere.
+ In this case, no user checking will be done. This is usefull to use this url to provide
+ other functionality appart of login, (such as logout)
+
+ :note: Keeping user information about group membership inside storage is highly recommended.
+ There will be calls to getGroups one an again, and also to getRealName, not just
+ at login, but at future (from admin interface, at user editing for example)
+ '''
+ return None
+
+ def getInfo(self, parameters):
+ '''
+ This method is invoked whenever the authinfo url is invoked, with the name of the authenticator
+ If this is implemented, information returned by this will be shown via web.
+
+ :note: You can return here a single element or a list (or tuple), where first element will be content itself,
+ and second will be the content type (i.e. "text/plain").
+ '''
+ return None
+
+ def getRealName(self, username):
+ '''
+ Tries to get the real name of an user
+
+ Default implementation returns just the same user name that is passed in.
+ '''
+ return username
+
+ def createUser(self, usrData):
+ '''
+ This method is used when creating an user to allow the authenticator:
+
+ * Check that the name inside usrData is fine
+ * Fill other (not name, if you don't know what are you doing) usrData dictionary values.
+
+ This will be invoked from admin interface, when admin wants to create a new user
+
+ modified usrData will be used to store values at database.
+
+ Args:
+ usrData: Contains data received from user directly, that is a dictionary
+ with at least: name, realName, comments, state & password.
+ This is an in/out parameter, so you can modify, for example,
+ **realName**
+
+ Returns:
+ Raises an exception if things didn't went fine,
+ return value is ignored, but modified usrData is used if this does not
+ raises an exception.
+
+ Take care with whatever you modify here, you can even modify provided
+ name (login name!) to a new one!
+
+ :note: If you have an SSO where you can't create an user from admin interface,
+ raise an exception here indicating that the creation can't be done.
+ Default implementation simply raises "AuthenticatorException" and
+ says that user can't be created manually
+
+ '''
+ raise InvalidUserException(translatable('Users can\'t be created inside this authenticator'))
+
+
+ def modifyUser(self, usrData):
+ '''
+ This method is used when modifying an user to allow the authenticator:
+
+ * Check that the name inside usrData is fine
+ * Fill other (not name, if you don't know what are you doing) usrData dictionary values.
+
+ Args:
+ usrData: Contains data received from user directly, that is a dictionary
+ with at least: name, realName, comments, state & password.
+ This is an in/out parameter, so you can modify, for example,
+ **realName**
+
+
+ Returns:
+ Raises an exception if things didn't went fine,
+ return value is ignored, but modified usrData is used if this does not
+ raises an exception.
+
+ Take care with whatever you modify here, you can even modify provided
+ name (login name!) to a new one!
+
+ :note: By default, this will do nothing, as we can only modify "accesory" internal
+ data of users.
+ '''
+ pass
+
+
+ def createGroup(self, groupData):
+ '''
+ This method is used when creating a new group to allow the authenticator:
+
+ * Check that the name inside groupData is fine
+ * Fill other (not name, if you don't know what are you doing) usrData dictionary values.
+
+ This will be invoked from admin interface, when admin wants to create a new group.
+
+ modified groupData will be used to store values at database.
+
+ Args:
+ groupData: Contains data received from user directly, that is a dictionary
+ with at least: name, comments and active.
+ This is an in/out parameter, so you can modify, for example,
+ **comments**
+
+ Returns:
+ Raises an exception if things didn't went fine,
+ return value is ignored, but modified groupData is used if this does not
+ raises an exception.
+
+ Take care with whatever you modify here, you can even modify provided
+ name (group name) to a new one!
+ '''
+ pass
+
+ def removeUser(self, username):
+ '''
+ Remove user is used whenever from the administration interface, or from other
+ internal workers, an user needs to be removed.
+
+ This is a notification method, whenever an user gets removed from UDS, this
+ will get called.
+
+ You can do here whatever you want, but you are not requested to do anything
+ at your authenticators.
+
+ If this method raises an exception, the user will not be removed from UDS
+ '''
+ pass
+
+ # We don't have a "modify" group option. Once u have created it, the only way of changing it if removing it an recreating it with another name
+
+ def removeGroup(self, groupname):
+ '''
+ Remove user is used whenever from the administration interface, or from other
+ internal workers, an group needs to be removed.
+
+ This is a notification method, whenever an group gets removed from UDS, this
+ will get called.
+
+ You can do here whatever you want, but you are not requested to do anything
+ at your authenticators.
+
+ If this method raises an exception, the group will not be removed from UDS
+ '''
+ pass
diff --git a/trunk/server/src/uds/core/auths/Exceptions.py b/trunk/server/src/uds/core/auths/Exceptions.py
new file mode 100644
index 00000000..5ada7bae
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/Exceptions.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+class AuthenticatorException(Exception):
+ '''
+ Generic authentication exception
+ '''
+ pass
+
+class InvalidUserException(Exception):
+ '''
+ Invalid user specified. The user cant access the requested service
+ '''
+ pass
+
+class InvalidAuthenticatorException(Exception):
+ '''
+ Invalida authenticator has been specified
+ '''
+ pass
+
+class Redirect(Exception):
+ '''
+ This exception indicates that a redirect is required.
+ Used in authUrlCallback to indicate that no use has been authenticated, but redirect is needed
+ '''
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/auths/Group.py b/trunk/server/src/uds/core/auths/Group.py
new file mode 100644
index 00000000..e1178abb
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/Group.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification,are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Group(object):
+ '''
+ A group is simply a database group associated with its authenticator instance
+
+ It's only constructor expect a database group as parameter.
+ '''
+
+ def __init__(self, dbGroup):
+ '''
+ Initializes internal data
+ '''
+ self._manager = dbGroup.getManager()
+ self._dbGroup = dbGroup
+
+ def manager(self):
+ '''
+ Returns the database authenticator associated with this group
+ '''
+ return self._manager
+
+ def dbGroup(self):
+ '''
+ Returns the database group associated with this
+ '''
+ return self._dbGroup
diff --git a/trunk/server/src/uds/core/auths/GroupsManager.py b/trunk/server/src/uds/core/auths/GroupsManager.py
new file mode 100644
index 00000000..84e4a574
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/GroupsManager.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.util.State import State
+from Group import Group
+import logging
+
+logger = logging.getLogger(__name__)
+
+class GroupsManager(object):
+ '''
+ Manages registered groups for an specific authenticator.
+
+ Most authenticators (except internal database one, that is an special case)
+ has their database of users and passwords outside UDS. Think, for example,
+ about LDAP. It has its own database of users and groups, and has its own
+ correspondence of which user belongs to which group.
+
+ UDS Only knows a subset of this groups, those that the administrator has
+ registered inside UDS.
+
+ To manage the equivalence between groups from the authenticator and UDS groups,
+ we provide a list of "known groups" by uds. The authenticator then makes the
+ correspondence, marking the groups (UDS groups) that the user belongs to as
+ valid.
+
+ Managed groups names are compared using case insensitive comparison.
+ '''
+
+ def __init__(self, dbAuthenticator):
+ '''
+ Initializes the groups manager.
+ The dbAuthenticator is the database record of the authenticator
+ to which this groupsManager will be associated
+ '''
+ self._groups = {} # We just get active groups, inactive aren't visible to this class
+ for g in dbAuthenticator.groups.filter(state = State.ACTIVE):
+ self._groups[g.name.lower()] = { 'group': Group(g), 'valid': False }
+
+ def contains(self, groupName):
+ '''
+ Returns true if this groups manager contains the specified group name (string)
+ '''
+ return self._groups.has_key(groupName.lower())
+
+ def getGroupsNames(self):
+ '''
+ Return all groups names managed by this groups manager. The names are returned
+ as where inserted inside Database (most probably using administration interface)
+ '''
+ for g in self._groups.itervalues():
+ yield g['group'].dbGroup().name
+
+ def getValidGroups(self):
+ '''
+ returns the list of valid groups (:py:class:uds.core.auths.Group.Group)
+ '''
+ res = []
+ for g in self._groups.itervalues():
+ if g['valid'] is True:
+ res.append(g['group'])
+ return res
+
+ def hasValidGroups(self):
+ '''
+ Checks if this groups manager has at least one group that has been
+ validated (using :py:meth:.validate)
+ '''
+ for g in self._groups.itervalues():
+ if g['valid'] is True:
+ return True
+ return False
+
+ def getGroup(self, groupName):
+ '''
+ If this groups manager contains that group manager, it returns the
+ :py:class:uds.core.auths.Group.Group representing that group name.
+ '''
+ if self._groups.has_key(groupName.lower()):
+ return self._groups[groupName.lower()]['group']
+ else:
+ return None
+
+ def validate(self, groupName):
+ '''
+ Validates that the group groupName passed in is valid for this group manager.
+
+ It check that the group specified is known by this group manager.
+
+ Args:
+ groupName: string, list or tuple of values (strings) to check
+
+ Returns nothing, it changes the groups this groups contains attributes,
+ so they reflect the known groups that are considered valid.
+ '''
+ if type(groupName) is tuple or type(groupName) is list:
+ for n in groupName:
+ self.validate(n)
+ else:
+ if self._groups.has_key(groupName.lower()):
+ self._groups[groupName.lower()]['valid'] = True
+
+ def isValid(self, groupName):
+ '''
+ Checks if this group name is marked as valid inside this groups manager.
+ Returns True if group name is marked as valid, False if it isn't.
+ '''
+ if self._groups.has_key(groupName.lower()):
+ return self._groups[groupName.lower()]['valid']
+ return False
+
+ def __str__(self):
+ return "Groupsmanager: {0}".format(self._groups)
+
+
diff --git a/trunk/server/src/uds/core/auths/User.py b/trunk/server/src/uds/core/auths/User.py
new file mode 100644
index 00000000..880c6ed5
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/User.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class User(object):
+ '''
+ An user represents a database user, associated with its authenticator (instance)
+ and its groups.
+ '''
+
+ def __init__(self, dbUser):
+ self._manager = dbUser.getManager()
+ self._grpsManager = None
+ self._dbUser = dbUser
+ self._groups = None
+
+
+ def _groupsManager(self):
+ '''
+ If the groups manager for this user already exists, it returns this.
+ If it does not exists, it creates one default from authenticator and
+ returns it.
+ '''
+ from GroupsManager import GroupsManager
+
+ if self._grpsManager == None:
+ self._grpsManager = GroupsManager(self._manager.dbAuthenticator())
+ return self._grpsManager
+
+ def groups(self):
+ '''
+ Returns the valid groups for this user.
+ To do this, it will validate groups throuht authenticator instance using
+ :py:meth:`uds.core.auths.Authenticator.getGroups` method.
+
+ :note: Once obtained valid groups, it caches them until object removal.
+ '''
+ from uds.models import User as DbUser
+ from Group import Group
+
+ if self._groups == None:
+ if self._manager.isExternalSource == True:
+ self._manager.getGroups(self._dbUser.name, self._groupsManager())
+ self._groups = self._groupsManager().getValidGroups()
+ # This is just for updating "cached" data of this user, we only get real groups at login and at modify user operation
+ usr = DbUser.objects.get(pk=self._dbUser.id)
+ usr.groups = [ g.dbGroup() for g in self._groups ]
+ else:
+ # From db
+ usr = DbUser.objects.get(pk=self._dbUser.id)
+ self._groups = []
+ for g in usr.groups.all():
+ self._groups.append(Group(g))
+ return self._groups
+
+
+ def manager(self):
+ '''
+ Returns the authenticator instance
+ '''
+ return self._manager
+
+ def dbUser(self):
+ '''
+ Returns the database user
+ '''
+ return self._dbUser
+
diff --git a/trunk/server/src/uds/core/auths/__init__.py b/trunk/server/src/uds/core/auths/__init__.py
new file mode 100644
index 00000000..605340f7
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/__init__.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS authentication related interfaces and classes
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from BaseAuthenticator import Authenticator
+from User import User
+from Group import Group
+from GroupsManager import GroupsManager
+import Exceptions
+
+def factory():
+ '''
+ Returns factory for register/access to authenticators
+ '''
+ from AuthsFactory import AuthsFactory
+ return AuthsFactory.factory()
+
+
diff --git a/trunk/server/src/uds/core/auths/auth.png b/trunk/server/src/uds/core/auths/auth.png
new file mode 100644
index 00000000..c8560b1d
Binary files /dev/null and b/trunk/server/src/uds/core/auths/auth.png differ
diff --git a/trunk/server/src/uds/core/auths/auth.py b/trunk/server/src/uds/core/auths/auth.py
new file mode 100644
index 00000000..de1554be
--- /dev/null
+++ b/trunk/server/src/uds/core/auths/auth.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Provides useful functions for authenticating, used by web interface.
+
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from functools import wraps
+from django.http import HttpResponseRedirect
+from uds.core.util.Config import GlobalConfig
+from uds.core import auths
+from uds.core.managers.CryptoManager import CryptoManager
+from uds.core.util.State import State
+from uds.models import User
+import logging
+
+logger = logging.getLogger(__name__)
+
+USER_KEY = 'uk'
+PASS_KEY = 'pk'
+
+def getIp(request):
+ '''
+ Obtains the IP of a Django Request, even behind a proxy
+
+ Returns the obtained IP, that is always be a valid ip address.
+ '''
+ try:
+ request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
+ except KeyError:
+ request.ip = request.META['REMOTE_ADDR']
+ return request.ip
+
+# Decorator to make easier protect pages
+def webLoginRequired(view_func):
+ '''
+ Decorator to set protection to acces page
+ To use this decorator, the view must receive 'response' and 'user'
+ example: view(response, user)
+ '''
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ '''
+ Wrapped function for decorator
+ '''
+ user = request.session.get(USER_KEY)
+ if user is not None:
+ try:
+ user = User.objects.get(pk=user)
+ except User.DoesNotExist:
+ user = None
+ if user is None:
+ url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
+ if GlobalConfig.REDIRECT_TO_HTTPS.getBool() is True:
+ url = url.replace('http://', 'https://')
+ logger.debug('No user found, redirecting to {0}'.format(url))
+ return HttpResponseRedirect(url)
+ # Refresh session duration
+ #request.session.set_expiry(GlobalConfig.USER_SESSION_LENGTH.getInt())
+ request.user = user
+ getIp(request)
+ return view_func(request, *args, **kwargs)
+ return _wrapped_view
+
+def __registerUser(authenticator, authInstance, username):
+ '''
+ Check if this user already exists on database with this authenticator, if don't, create it with defaults
+ This will work correctly with both internal or externals cause we first authenticate the user, if internal and user do not exists in database
+ authenticate will return false, if external and return true, will create a reference in database
+ '''
+ usr = authenticator.getOrCreateUser(username, authInstance.getRealName(username))
+ if usr is not None and State.isActive(usr.state):
+ # Now we update database groups for this user
+ usr.getManager().recreateGroups(usr)
+ return usr
+
+ return None
+
+
+def authenticate(username, password, authenticator, useInternalAuthenticate = False):
+ '''
+ Given an username, password and authenticator, try to authenticate user
+ @param username: username to authenticate
+ @param password: password to authenticate this user
+ @param authenticator: Authenticator (database object) used to authenticate with provided credentials
+ @param useInternalAuthenticate: If True, tries to authenticate user using "internalAuthenticate". If false, it uses "authenticate".
+ This is so because in some situations we may want to use a "trusted" method (internalAuthenticate is never invoked directly from web)
+ @return: None if authentication fails, User object (database object) if authentication is o.k.
+ '''
+ logger.debug('Authenticating user {0} with authenticator {1}'.format(username, authenticator))
+ gm = auths.GroupsManager(authenticator)
+ authInstance = authenticator.getInstance()
+ if useInternalAuthenticate is False:
+ res = authInstance.authenticate(username, password, gm)
+ else:
+ res = authInstance.internalAuthenticate(username, password, gm)
+
+ if res is False:
+ return None
+
+ logger.debug('Groups manager: {0}'.format(gm))
+
+ # If do not have any valid group
+ if gm.hasValidGroups() is False:
+ return None
+
+ return __registerUser(authenticator, authInstance, username)
+
+
+def authenticateViaCallback(authenticator, params):
+ '''
+ Given an username, this method will get invoked whenever the url for a callback
+ for an authenticator is requested.
+
+ The idea behind this is that, with authenticators that are based on url redirections
+ (SSO auths), we provide a mechanism to allow the authenticator to login the user.
+
+ This will:
+ * Check that the authenticator supports a callback, raise an error if it
+ doesn't support it.
+ * Invoke authenticator callback, and expects, on exit, a valid username.
+ If it gets None or '', it will raise an error.
+ * Register user inside uds if necesary, will invoke in the process
+ **getRealUsername** to get it, so keep it wher you can recover it.
+ * Update user group membership using Authenticator getGroups, so, in your
+ callbacks, remember to store (using provided environment storage, for example)
+ the groups of this user so your getGroups will work correctly.
+ '''
+ gm = auths.GroupsManager(authenticator)
+ authInstance = authenticator.getInstance()
+
+ # If there is no callback for this authenticator...
+ if authInstance.authCallback == auths.Authenticator.authCallback:
+ raise auths.Exceptions.InvalidAuthenticatorException()
+
+ username = authInstance.authCallback(params, gm)
+
+ if username is None or username == '' or gm.hasValidGroups() is False:
+ raise auths.Exceptions.InvalidUserException('User don\'t has access to UDS')
+
+ return __registerUser(authenticator, authInstance, username)
+
+def authCallbackUrl(authenticator):
+ '''
+ Helper method, so we can get the auth call back url for an authenticator
+ '''
+ from django.core.urlresolvers import reverse
+ return reverse('uds.web.views.authCallback', kwargs={'authName': authenticator.name})
+
+def authInfoUrl(authenticator):
+ '''
+ Helper method, so we can get the info url for an authenticator
+ '''
+ from django.core.urlresolvers import reverse
+ if type(authenticator) is str:
+ name = authenticator
+ else:
+ name = authenticator.name
+
+ return reverse('uds.web.views.authInfo', kwargs={'authName': name})
+
+def webLogin(request, response, user, password):
+ '''
+ Helper function to, once the user is authenticated, store the information at the user session.
+ @return: Always returns True
+ '''
+ user.updateLastAccess()
+ request.session.clear()
+ request.session[USER_KEY] = user.id
+ request.session[PASS_KEY] = CryptoManager.manager().xor(password.encode('utf-8'), request.COOKIES['uds'])
+ return True
+
+
+def webPassword(request):
+ '''
+ The password is stored at session using a simple scramble algorithm that keeps the password splited at
+ session (db) and client browser cookies. This method uses this two values to recompose the user password
+ so we can provide it to remote sessions.
+ @param request: DJango Request
+ @return: Unscrambled user password
+ '''
+ return CryptoManager.manager().xor(request.session.get(PASS_KEY), request.COOKIES['uds']).decode('utf-8')
+
+def webLogout(request, exit_url = None):
+ '''
+ Helper function to clear user related data from session. If this method is not used, the session we be cleaned anyway
+ by django in regular basis.
+ '''
+ # Invoke esit for authenticator
+ request.session.clear()
+ if exit_url is None:
+ exit_url = GlobalConfig.LOGIN_URL.get()
+ # Try to delete session
+ return HttpResponseRedirect(request.build_absolute_uri(exit_url))
+
diff --git a/trunk/server/src/uds/core/jobs/DelayedTask.py b/trunk/server/src/uds/core/jobs/DelayedTask.py
new file mode 100644
index 00000000..1e371347
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/DelayedTask.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.Environment import Environmentable
+import logging
+
+
+logger = logging.getLogger(__name__)
+
+class DelayedTask(Environmentable):
+ def __init__(self):
+ '''
+ Remember to invoke parent init in derived clases using super(myClass,self).__init__() to let this initialize its own variables
+ '''
+ Environmentable.__init__(self, None)
+
+ def execute(self):
+ try:
+ self.run()
+ except Exception, e:
+ logger.error('Job {0} raised an exception: {1}'.format(self.__class__, e))
+
+ def run(self):
+ '''
+ You must provide your own "run" method to do whatever you need
+ '''
+ logging.debug("Base run of job called for class")
diff --git a/trunk/server/src/uds/core/jobs/DelayedTaskRunner.py b/trunk/server/src/uds/core/jobs/DelayedTaskRunner.py
new file mode 100644
index 00000000..5e1eb787
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/DelayedTaskRunner.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import transaction
+from django.db.models import Q
+from uds.models import DelayedTask as dbDelayedTask
+from uds.core.util.Decorators import retryOnException
+from ..Environment import Environment
+from socket import gethostname
+from pickle import loads, dumps
+from datetime import datetime, timedelta
+import threading, time
+import logging
+
+logger = logging.getLogger(__name__)
+
+class DelayedTaskThread(threading.Thread):
+ def __init__(self, taskInstance):
+ super(DelayedTaskThread,self).__init__()
+ self._taskInstance = taskInstance
+
+ def run(self):
+ try:
+ self._taskInstance.execute()
+ except Exception, e:
+ logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
+
+class DelayedTaskRunner(object):
+ CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
+ # How often tasks r checked
+ granularity = 2
+
+ # to keep singleton DelayedTaskRunner
+ _runner = None
+
+ def __init__(self):
+ logger.debug("Initializing delayed task runner")
+ self._hostname = gethostname()
+ self._keepRunning = True
+
+ def notifyTermination(self):
+ self._keepRunning = False
+
+ @staticmethod
+ def runner():
+ if DelayedTaskRunner._runner == None:
+ DelayedTaskRunner._runner = DelayedTaskRunner()
+ return DelayedTaskRunner._runner
+
+ @transaction.commit_manually
+ def executeOneDelayedTask(self):
+ now = datetime.now()
+ filt = Q(execution_time__lt=now) | Q(insert_date__gt=now)
+ # If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
+ taskInstance = None
+ try:
+ task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0]
+ task.delete()
+ transaction.commit()
+ taskInstance = loads(task.instance.decode(self.CODEC))
+ except Exception:
+ # No task waiting, nice
+ transaction.rollback()
+
+ if taskInstance != None:
+ env = Environment.getEnvForType(taskInstance.__class__)
+ taskInstance.setEnv(env)
+ DelayedTaskThread(taskInstance).start()
+
+ @transaction.commit_on_success
+ def __insert(self, instance, delay, tag):
+ now = datetime.now()
+ exec_time = now + timedelta(seconds = delay)
+ cls = instance.__class__
+ dbDelayedTask.objects.create(type = str(cls.__module__ + '.' + cls.__name__), instance = dumps(instance).encode(self.CODEC),
+ insert_date = now, execution_delay = delay, execution_time = exec_time, tag = tag)
+
+ def insert(self, instance, delay, tag = ''):
+ retries = 3
+ while retries > 0:
+ retries -= 1
+ try:
+ self.__insert(instance, delay, tag)
+ break
+ except Exception, e:
+ logger.info('Exception inserting a delayed task {0}: {1}'.format(str(e.__class__), e))
+ # If retries == 0, this is a big error
+ if retries == 0:
+ logger.error("Could not insert delayed task!!!! {0} {1} {2}".format(instance, delay, tag))
+ return False
+ return True
+
+ @transaction.commit_on_success
+ def remove(self, tag):
+ try:
+ dbDelayedTask.objects.select_for_update().filter(tag=tag).delete()
+ except Exception as e:
+ logger.exception('Exception removing a delayed task {0}: {1}'.format(str(e.__class__), e))
+
+ @transaction.commit_on_success
+ def checkExists(self, tag):
+ number = 0
+ try:
+ number = dbDelayedTask.objects.filter(tag=tag).count()
+ except Exception as e:
+ logger.error('Exception looking for a delayed task tag {0}'.format(tag))
+ return number > 0
+
+ def run(self):
+ logger.debug("At loop")
+ while self._keepRunning:
+ try:
+ time.sleep(self.granularity)
+ self.executeOneDelayedTask()
+ except Exception, e:
+ logger.error('Unexpected exception at run loop {0}: {1}'.format(e.__class__, e))
diff --git a/trunk/server/src/uds/core/jobs/Job.py b/trunk/server/src/uds/core/jobs/Job.py
new file mode 100644
index 00000000..6b47096c
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/Job.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.models import Scheduler
+from uds.core import Environmentable
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Job(Environmentable):
+ # Default frecuency, once a day. Remenber that precision will be based on "granurality" of Scheduler
+ # If a job is used for delayed execution, this attribute is in fact ignored
+ frecuency = Scheduler.DAY
+
+ def __init__(self, environment):
+ '''
+ Remember to invoke parent init in derived clases using super(myClass,self).__init__(environmnet) if u want to use env(), cache() and storage() methods
+ '''
+ Environmentable.__init__(self, environment)
+
+ def execute(self):
+ try:
+ self.run()
+ except Exception, e:
+ logger.exception('Job {0} raised an exception:'.format(self.__class__))
+
+ def run(self):
+ '''
+ You must provide your own "run" method to do whatever you need
+ '''
+ logging.debug("Base run of job called for class")
+ pass
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/jobs/JobsFactory.py b/trunk/server/src/uds/core/jobs/JobsFactory.py
new file mode 100644
index 00000000..223d6a69
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/JobsFactory.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class JobsFactory(object):
+ _factory = None
+
+ def __init__(self):
+ self._jobs = {}
+
+ @staticmethod
+ def factory():
+ if JobsFactory._factory == None:
+ JobsFactory._factory = JobsFactory()
+ return JobsFactory._factory
+
+ def jobs(self):
+ return self._jobs
+
+ def insert(self, name, type_):
+ logger.debug('Inserting job {0} of type {1}'.format(name, type_))
+ try:
+ self._jobs[name] = type_
+ except Exception, e:
+ logger.debug('Exception at insert in JobsFactory: {0}, {1}'.format(e.__class__, e))
+
+ def ensureJobsInDatabase(self):
+ from uds.models import Scheduler, getSqlDatetime
+ from uds.core.util.State import State
+
+ try:
+ logger.debug('Ensuring that jobs are registered inside database')
+ for name, type_ in self._jobs.iteritems():
+ try:
+ # We use database server datetime
+ now = getSqlDatetime()
+ next_ = now
+ job = Scheduler.objects.create(name = name, frecuency = type_.frecuency, last_execution = now, next_execution = next_, state = State.FOR_EXECUTE)
+ except Exception: # already exists
+ logger.debug('Already added {0}'.format(name))
+ job = Scheduler.objects.get(name=name)
+ job.frecuency = type_.frecuency
+ job.save()
+ except Exception, e:
+ logger.debug('Exception at ensureJobsInDatabase in JobsFactory: {0}, {1}'.format(e.__class__, e))
+
+
+ def lookup(self, typeName):
+ try:
+ return self._jobs[typeName]
+ except KeyError:
+ return None
diff --git a/trunk/server/src/uds/core/jobs/Scheduler.py b/trunk/server/src/uds/core/jobs/Scheduler.py
new file mode 100644
index 00000000..2aa8f43b
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/Scheduler.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db.models import Q
+from django.db import transaction, DatabaseError
+from uds.models import Scheduler as dbScheduler, getSqlDatetime, State
+from uds.core.jobs.JobsFactory import JobsFactory
+from datetime import timedelta
+from socket import gethostname
+import threading, time
+import logging
+
+logger = logging.getLogger(__name__)
+
+class JobThread(threading.Thread):
+ def __init__(self, jobInstance, dbJob):
+ super(JobThread,self).__init__()
+ self._jobInstance = jobInstance
+ self._dbJobId = dbJob.id
+
+ def run(self):
+ try:
+ self._jobInstance.execute()
+ except Exception:
+ logger.debug("Exception executing job {0}".format(self._dbJobId))
+ self.jobDone()
+
+ def jobDone(self):
+ done = False
+ while done is False:
+ try:
+ self.__updateDb()
+ done = True
+ except:
+ # Erased from database, nothing hapens
+ # logger.exception(e)
+ logger.info('Database access locked... Retrying')
+ time.sleep(1)
+
+
+ @transaction.commit_on_success
+ def __updateDb(self):
+ job = dbScheduler.objects.select_for_update().get(id=self._dbJobId)
+ job.state = State.FOR_EXECUTE
+ job.owner_server = ''
+ job.next_execution = getSqlDatetime() + timedelta(seconds = job.frecuency)
+ # Update state and last execution time at database
+ job.save()
+
+class Scheduler(object):
+ granularity = 2 # We check for cron jobs every THIS seconds
+
+ # to keep singleton Scheduler
+ _scheduler = None
+
+ def __init__(self):
+ self._hostname = gethostname()
+ self._keepRunning = True
+
+ @staticmethod
+ def scheduler():
+ if Scheduler._scheduler == None:
+ Scheduler._scheduler = Scheduler()
+ return Scheduler._scheduler
+
+ def notifyTermination(self):
+ self._keepRunning = False
+
+ @transaction.commit_manually
+ def executeOneJob(self):
+ '''
+ Looks for a job and executes it
+ '''
+ jobInstance = None
+ try:
+ now = getSqlDatetime() # Datetimes are based on database server times
+ filter = Q(state = State.FOR_EXECUTE) & (Q(owner_server = self._hostname) | Q(owner_server = '')) & (Q(last_execution__gt = now) | Q(next_execution__lt = now))
+ # If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
+ # This params are all set inside filter (look at __init__)
+ job = dbScheduler.objects.select_for_update().filter(filter).order_by('next_execution')[0]
+ jobInstance = job.getInstance()
+
+ if jobInstance == None:
+ logger.error('Job instance can\'t be resolved for {0}, removing it'.format(job))
+ job.delete()
+ transaction.commit()
+ return
+ logger.debug('Executing job:>{0}<'.format(job.name))
+ job.state = State.RUNNING
+ job.owner_server = self._hostname
+ job.last_execution = now
+ job.save()
+ transaction.commit()
+ JobThread(jobInstance, job).start() # Do not instatiate thread, just run it
+ except IndexError:
+ transaction.rollback()
+ # Do nothing, there is no jobs for execution
+ return
+ except DatabaseError:
+ # Whis will happen whenever a connection error or a deadlock error happens
+ # This in fact means that we have to retry operation, and retry will happen on main loop
+ # Look at this http://dev.mysql.com/doc/refman/5.0/en/innodb-deadlocks.html
+ # I have got some deadlock errors, but looking at that url, i found that it is not so abnormal
+ logger.debug('Deadlock, no problem at all :-) (sounds hards, but really, no problem)')
+ transaction.rollback() # So django do not complains about this
+
+ @transaction.commit_on_success
+ def releaseOwnShedules(self):
+ '''
+ Releases all scheduleds being executed by this scheduler
+ '''
+ dbScheduler.objects.select_for_update().filter(owner_server = self._hostname).update(owner_server = '', state = State.FOR_EXECUTE)
+
+
+ def run(self):
+ # We ensure that the jobs are also in database so we can
+ logger.debug('Run Scheduler thread')
+ JobsFactory.factory().ensureJobsInDatabase()
+ self.releaseOwnShedules()
+ logger.debug("At loop")
+ while self._keepRunning:
+ try:
+ time.sleep(self.granularity)
+ self.executeOneJob()
+ except Exception, e:
+ logger.exception('Unexpected exception at run loop {0}: {1}'.format(e.__class__, e))
+
diff --git a/trunk/server/src/uds/core/jobs/__init__.py b/trunk/server/src/uds/core/jobs/__init__.py
new file mode 100644
index 00000000..c1aea440
--- /dev/null
+++ b/trunk/server/src/uds/core/jobs/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS jobs related modules
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from Job import Job
+from DelayedTask import DelayedTask
+
+def factory():
+ from JobsFactory import JobsFactory
+ return JobsFactory.factory()
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/managers/CryptoManager.py b/trunk/server/src/uds/core/managers/CryptoManager.py
new file mode 100644
index 00000000..e0beacd7
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/CryptoManager.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from server.settings import RSA_KEY
+from Crypto.PublicKey import RSA
+from OpenSSL import crypto
+from Crypto.Random import atfork
+import hashlib, array
+
+# To generate an rsa key, first we need the crypt module
+# next, we do:
+# from Crypto.PublicKey import RSA
+# import os
+# RSA.generate(1024, os.urandom).exportKey()
+
+class CryptoManager(object):
+ CODEC = 'base64'
+
+ instance = None
+
+ def __init__(self):
+ self._rsa = RSA.importKey(RSA_KEY)
+
+ @staticmethod
+ def manager():
+ if CryptoManager.instance is None:
+ CryptoManager.instance = CryptoManager()
+ return CryptoManager.instance
+
+ def encrypt(self, string):
+ atfork()
+ return self._rsa.encrypt(string, '')[0].encode(CryptoManager.CODEC)
+
+ def decrypt(self, string):
+ atfork()
+ return self._rsa.decrypt(string.decode(CryptoManager.CODEC))
+
+ def xor(self, s1, s2):
+ mult = (len(s1)/len(s2)) + 1
+ s1 = array.array('B', s1)
+ s2 = array.array('B', s2 * mult)
+ return array.array('B', (s1[i] ^ s2[i] for i in range(len(s1)))).tostring()
+
+ def loadPrivateKey(self, rsaKey):
+ try:
+ pk = RSA.importKey(rsaKey)
+ except Exception as e:
+ raise e
+ return pk
+
+ def loadCertificate(self,certificate):
+ try:
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
+ except crypto.Error as e:
+ raise Exception(e.message[0][2])
+ return cert
+
+ def certificateString(self, certificate):
+ return certificate.replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace('\n', '')
+
+
+ def hash(self, string):
+ if string is '' or string is None:
+ return ''
+ return hashlib.sha1(string).hexdigest()
diff --git a/trunk/server/src/uds/core/managers/DownloadsManager.py b/trunk/server/src/uds/core/managers/DownloadsManager.py
new file mode 100644
index 00000000..7590df8a
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/DownloadsManager.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import os, tempfile, zipfile, uuid
+from django.http import HttpResponse, Http404
+from django.core.servers.basehttp import FileWrapper
+import logging
+
+logger = logging.getLogger(__name__)
+
+class DownloadsManager(object):
+ '''
+ Manager so connectors can register their own downloadables
+ For registering, use at __init__.py of the conecto something like this:
+ from uds.core.managers.DownloadsManager import DownloadsManager
+ import os.path, sys
+ DownloadsManager.manager().registerDownloadable('test.exe',
+ _('comments for test'),
+ os.path.dirname(sys.modules[__package__].__file__) + '/files/test.exe',
+ 'application/x-msdos-program')
+ '''
+ _manager = None
+
+ def __init__(self):
+ self._downloadables = {}
+ self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
+
+ @staticmethod
+ def manager():
+ if DownloadsManager._manager == None:
+ DownloadsManager._manager = DownloadsManager()
+ return DownloadsManager._manager
+
+
+ def registerDownloadable(self, name, comment, path, mime = 'application/octet-stream'):
+ '''
+ Registers a downloadable file.
+ @param name: name shown
+ @param path: path to file
+ @params zip: If download as zip
+ '''
+ _id = str(uuid.uuid5(self._namespace, name))
+ self._downloadables[_id] = { 'name': name, 'comment' : comment, 'path' : path, 'mime' : mime }
+
+ def getDownloadables(self):
+ return self._downloadables
+
+
+ def send(self, request, _id):
+ if self._downloadables.has_key(_id) is False:
+ return Http404()
+ return self.__send_file(request, self._downloadables[_id]['name'], self._downloadables[_id]['path'], self._downloadables[_id]['mime']);
+
+ def __send_file(self, request, name, filename, mime):
+ """
+ Send a file through Django without loading the whole file into
+ memory at once. The FileWrapper will turn the file object into an
+ iterator for chunks of 8KB.
+ """
+ wrapper = FileWrapper(file(filename))
+ response = HttpResponse(wrapper, content_type=mime)
+ response['Content-Length'] = os.path.getsize(filename)
+ response['Content-Disposition'] = 'attachment; filename=' + name
+ return response
+
diff --git a/trunk/server/src/uds/core/managers/LogManager.py b/trunk/server/src/uds/core/managers/LogManager.py
new file mode 100644
index 00000000..d7ed3590
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/LogManager.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2013 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.models import Log
+from uds.core.util import log
+from uds.core.util.Config import GlobalConfig
+import logging
+
+logger = logging.getLogger(__name__)
+
+OT_USERSERVICE, OT_PUBLICATION, OT_DEPLOYED_SERVICE, OT_SERVICE, OT_PROVIDER, OT_USER, OT_GROUP, OT_AUTHENTICATOR = xrange(8)
+
+class LogManager(object):
+ '''
+ Manager for logging (at database) events
+ '''
+ _manager = None
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def manager():
+ if LogManager._manager == None:
+ LogManager._manager = LogManager()
+ return LogManager._manager
+
+
+ # User Service log section
+ def __logUserService(self, userService, level, message, source):
+ '''
+ Logs a message associated to an user service
+ '''
+ from uds.models import getSqlDatetime
+
+
+ qs = Log.objects.filter(owner_id = userService.id, owner_type = OT_USERSERVICE)
+ # First, ensure we do not have more than requested logs, and we can put one more log item
+ if qs.count() >= GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt():
+ for i in qs.order_by('-created',)[GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt()-1:]: i.delete()
+
+ # now, we add new log
+ Log.objects.create(owner_type = OT_USERSERVICE, owner_id = userService.id, created = getSqlDatetime(), source = source, level = level, data = message)
+
+ def __getUserServiceLogs(self, userService, limit):
+ '''
+ Get all logs associated with an user service, ordered by date
+ '''
+ qs = Log.objects.filter(owner_id = userService.id, owner_type = OT_USERSERVICE)
+ return [{'date': x.created, 'level': x.level, 'source': x.source, 'message': x.data} for x in qs.order_by('created')][:limit]
+
+
+ def doLog(self, wichObject, level, message, source = log.UNKNOWN):
+ '''
+ Do the logging for the requested object.
+
+ If the object provided do not accepts associated loggin, it simply ignores the request
+ '''
+ from uds.models import UserService
+
+ if type(level) is not int:
+ level = log.logLevelFromStr(level)
+
+ if type(wichObject) is UserService:
+ self.__logUserService(wichObject, level, message, source)
+ else:
+ logger.debug('Requested doLog for a type of object not covered: {0}'.format(wichObject))
+
+
+ def getLogs(self, wichObject, limit = GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt()):
+ from uds.models import UserService
+
+ if type(wichObject) is UserService:
+ return self.__getUserServiceLogs(wichObject, limit)
+ else:
+ logger.debug('Requested getLogs for a type of object not covered: {0}'.format(wichObject))
diff --git a/trunk/server/src/uds/core/managers/PublicationManager.py b/trunk/server/src/uds/core/managers/PublicationManager.py
new file mode 100644
index 00000000..5e708719
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/PublicationManager.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from django.db import transaction
+from uds.core.jobs.DelayedTask import DelayedTask
+from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
+from uds.core.services.Exceptions import PublishException
+from uds.models import DeployedServicePublication, getSqlDatetime, State
+import logging
+
+logger = logging.getLogger(__name__)
+
+PUBTAG = 'pm-'
+
+class PublicationLauncher(DelayedTask):
+ def __init__(self, publish):
+ super(PublicationLauncher,self).__init__()
+ self._publishId = publish.id
+
+ @transaction.commit_on_success
+ def run(self):
+ logger.debug('Publishing')
+ try:
+ dsp = DeployedServicePublication.objects.select_for_update().get(pk=self._publishId)
+ if dsp.state != State.LAUNCHING: # If not preparing (may has been canceled by user) just return
+ return
+ dsp.state = State.PREPARING
+ pi = dsp.getInstance()
+ state = pi.publish()
+ deployedService = dsp.deployed_service
+ deployedService.current_pub_revision += 1
+ deployedService.save()
+ PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
+ except Exception as e:
+ logger.exception("Exception launching publication")
+ dsp.state = State.ERROR
+ dsp.save()
+
+
+# Delayed Task that checks if a publication is done
+class PublicationFinishChecker(DelayedTask):
+ def __init__(self, publish):
+ super(PublicationFinishChecker,self).__init__()
+ self._publishId = publish.id
+ self._state = publish.state
+
+ @staticmethod
+ def checkAndUpdateState(dsp, pi, state):
+ '''
+ Checks the value returned from invocation to publish or checkPublishingState, updating the dsp database object
+ Return True if it has to continue checking, False if finished
+ '''
+ prevState = dsp.state
+ checkLater = False
+ if State.isFinished(state):
+ # Now we mark, if it exists, the previous usable publication as "Removable"
+ if State.isPreparing(prevState):
+ dsp.deployed_service.publications.filter(state=State.USABLE).update(state=State.REMOVABLE)
+ dsp.setState(State.USABLE)
+ dsp.deployed_service.markOldDeployedServicesAsRemovables(dsp)
+ elif State.isRemoving(prevState):
+ dsp.setState(State.REMOVED)
+ else: # State is canceling
+ dsp.setState(State.CANCELED)
+ # Mark all previous publications deployed services as removables
+ # and make this usable
+ pi.finish()
+ dsp.updateData(pi)
+ elif State.isErrored(state):
+ dsp.updateData(pi)
+ dsp.state = State.ERROR
+ else:
+ checkLater = True # The task is running
+ dsp.updateData(pi)
+
+ dsp.save()
+ if checkLater:
+ PublicationFinishChecker.checkLater(dsp, pi)
+
+ @staticmethod
+ def checkLater(dsp, pi):
+ '''
+ Inserts a task in the delayedTaskRunner so we can check the state of this publication
+ @param dps: Database object for DeployedServicePublication
+ @param pi: Instance of Publication manager for the object
+ '''
+ DelayedTaskRunner.runner().insert(PublicationFinishChecker(dsp), pi.suggestedTime, PUBTAG + str(dsp.id))
+
+ @transaction.commit_on_success
+ def run(self):
+ logger.debug('Checking publication finished {0}'.format(self._publishId))
+ try :
+ dsp = DeployedServicePublication.objects.select_for_update().get(pk=self._publishId)
+ if dsp.state != self._state:
+ logger.debug('Task overrided by another task (state of item changed)')
+ else:
+ pi = dsp.getInstance()
+ logger.debug("publication instance class: {0}".format(pi.__class__))
+ state = pi.checkState()
+ PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
+ except Exception, e:
+ logger.debug('Deployed service not found (erased from database) {0} : {1}'.format(e.__class__, e))
+
+
+class PublicationManager(object):
+ _manager = None
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def manager():
+ if PublicationManager._manager == None:
+ PublicationManager._manager = PublicationManager()
+ return PublicationManager._manager
+
+
+ @transaction.commit_on_success
+ def publish(self, deployedService):
+ if deployedService.publications.select_for_update().filter(state__in=State.PUBLISH_STATES).count() > 0:
+ raise PublishException(_('Already publishing. Wait for previous publication to finish and try again'))
+ try:
+ now = getSqlDatetime()
+ dsp = deployedService.publications.create(state = State.LAUNCHING, state_date = now, publish_date = now, revision = deployedService.current_pub_revision)
+ DelayedTaskRunner.runner().insert(PublicationLauncher(dsp), 4, PUBTAG + str(dsp.id))
+ except Exception as e:
+ logger.debug('Caught exception at publish: {0}'.format(e))
+ raise PublishException(str(e))
+
+ @transaction.commit_on_success
+ def cancel(self,dsp):
+ dsp = DeployedServicePublication.objects.select_for_update().get(id=dsp.id)
+ if dsp.state not in State.PUBLISH_STATES:
+ raise PublishException(_('Can\'t cancel non running publication'))
+
+ if dsp.state == State.LAUNCHING:
+ dsp.state = State.CANCELED
+ dsp.save()
+ return dsp
+
+ try:
+ pi = dsp.getInstance()
+ state = pi.cancel()
+ dsp.setState(State.CANCELING)
+ PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
+ return dsp
+ except Exception, e:
+ raise PublishException(str(e))
+
+ def unpublish(self, dsp):
+ if State.isUsable(dsp.state) == False and State.isRemovable(dsp.state) == False:
+ raise PublishException(_('Can\'t unpublish non usable publication'))
+ # TODO: Call assignation manager to remove removable items
+ if dsp.userServices.exclude(state__in=State.INFO_STATES).count() > 0:
+ raise PublishException(_('Can\'t unpublish publications with services in process'))
+ try:
+ pi = dsp.getInstance()
+ state = pi.destroy()
+ dsp.setState(State.REMOVING)
+ PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
+ except Exception, e:
+ raise PublishException(str(e))
+
diff --git a/trunk/server/src/uds/core/managers/TaskManager.py b/trunk/server/src/uds/core/managers/TaskManager.py
new file mode 100644
index 00000000..20495d35
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/TaskManager.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core.jobs.Scheduler import Scheduler
+from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
+from uds.core import jobs
+from uds.core.util.Config import GlobalConfig
+import threading, time, signal
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SchedulerThread(threading.Thread):
+ def run(self):
+ Scheduler.scheduler().run()
+
+ def notifyTermination(self):
+ Scheduler.scheduler().notifyTermination()
+
+class DelayedTaskThread(threading.Thread):
+ def run(self):
+ DelayedTaskRunner.runner().run()
+
+ def notifyTermination(self):
+ DelayedTaskRunner.runner().notifyTermination()
+
+
+class TaskManager(object):
+ keepRunning = True
+
+ @staticmethod
+ def sigTerm(sigNum, frame):
+ '''
+ This method will ensure that we finish correctly current running task before exiting.
+ If we need to stop cause something went wrong (that should not happen), we must send sigterm, wait a while (10-20 secs) and after that send sigkill
+ kill task
+ sleep 10
+ kill -9 task
+ Take a look at killTaskManager.sh :-)
+ '''
+ logger.info("Caught term signal, finishing task manager")
+ TaskManager.keepRunning = False
+
+ @staticmethod
+ def registerJob(jobName, jobType):
+ jobs.factory().insert(jobName, jobType)
+
+
+ @staticmethod
+ def registerScheduledTasks():
+ from uds.core.workers.ServiceCacheUpdater import ServiceCacheUpdater
+ from uds.core.workers.UserServiceCleaner import UserServiceInfoItemsCleaner, UserServiceRemover
+ from uds.core.workers.PublicationCleaner import PublicationInfoItemsCleaner, PublicationCleaner
+ from uds.core.workers.CacheCleaner import CacheCleaner
+ from uds.core.workers.DeployedServiceCleaner import DeployedServiceInfoItemsCleaner, DeployedServiceRemover
+
+ logger.info("Registering sheduled tasks")
+ TaskManager.registerJob('Service Cache Updater', ServiceCacheUpdater)
+ TaskManager.registerJob('User Service Info Cleaner', UserServiceInfoItemsCleaner)
+ TaskManager.registerJob('User Service Cleaner', UserServiceRemover)
+ TaskManager.registerJob('Publications Info Cleaner', PublicationInfoItemsCleaner)
+ TaskManager.registerJob('Publication Cleaner', PublicationCleaner)
+ TaskManager.registerJob('Utility Cache Cleaner', CacheCleaner)
+ TaskManager.registerJob('Deployed Service Info Cleaner', DeployedServiceInfoItemsCleaner)
+ TaskManager.registerJob('Deployed Service Cleaner', DeployedServiceRemover)
+
+
+
+ @staticmethod
+ def run():
+ TaskManager.keepRunning = True
+ # Runs Scheduler in a separate thread and DelayedTasks here
+
+ noSchedulers = GlobalConfig.SCHEDULER_THREADS.getInt()
+ noDelayedTasks = GlobalConfig.DELAYED_TASKS_THREADS.getInt()
+
+ threads = []
+ for n in range(noSchedulers):
+ thread = SchedulerThread()
+ thread.start()
+ threads.append(thread)
+ time.sleep(0.5)
+
+ for n in range(noDelayedTasks):
+ thread = DelayedTaskThread()
+ thread.start()
+ threads.append(thread)
+ time.sleep(1)
+
+ signal.signal(signal.SIGTERM, TaskManager.sigTerm)
+
+
+ # Debugging stuff
+ #import guppy
+ #from guppy.heapy import Remote
+ #Remote.on()
+
+ #gc.set_debug(gc.DEBUG_LEAK)
+ while( TaskManager.keepRunning ):
+ time.sleep(1)
+
+ for thread in threads:
+ thread.notifyTermination()
+
+ # The join of threads will happen before termination, so its fine to just return here
+
diff --git a/trunk/server/src/uds/core/managers/UserPrefsManager.py b/trunk/server/src/uds/core/managers/UserPrefsManager.py
new file mode 100644
index 00000000..ee77b473
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/UserPrefsManager.py
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django import forms
+from django.utils.translation import ugettext as _, ugettext_lazy
+from uds.models import UserPreference
+from uds.core.ui.UserInterface import gui
+import logging
+
+logger = logging.getLogger(__name__)
+
+class UserPrefsManager(object):
+ _manager = None
+
+ def __init__(self):
+ self._prefs = {}
+
+ @staticmethod
+ def manager():
+ if UserPrefsManager._manager == None:
+ UserPrefsManager._manager = UserPrefsManager()
+ return UserPrefsManager._manager
+
+ def __nameFor(self, module, name):
+ return module + "_" + name
+
+ def registerPrefs(self, modName, friendlyModName, prefs):
+ '''
+ Register an array of preferences for a module
+ '''
+ self._prefs[modName] = { 'friendlyName' : friendlyModName, 'prefs' : prefs }
+
+ def getPreferencesForUser(self, modName, user):
+ '''
+ Gets the preferences for an specified module for the user
+ '''
+ prefs = {}
+ for up in user.preferences.filter(module=modName):
+ prefs[up.name] = up.value
+ for p in self._prefs[modName]['prefs']:
+ if prefs.has_key(p.getName()) is False:
+ prefs[p.getName()] = p.getDefValue()
+ return prefs
+
+ def getHtmlForUserPreferences(self, user):
+ # First fill data for all preferences
+ data = {}
+ for up in user.preferences.all():
+ data[self.__nameFor(up.module, up.name)] = up.value
+ res = ''
+ for mod, v in self._prefs.iteritems():
+ form = forms.Form()
+ for p in v['prefs']:
+ name = self.__nameFor(mod, p.getName())
+ val = data[name] if data.has_key(name) else p.getDefValue()
+ form.fields[ name ] = p.formField(val)
+ res += ''
+ return res
+
+ def getGuiForUserPreferences(self, user=None):
+ data = {}
+ if user is not None:
+ for up in user.preferences.all():
+ data[self.__nameFor(up.module, up.name)] = up.value
+ res = []
+ for mod, v in self._prefs.iteritems():
+ grp = []
+ for p in v['prefs']:
+ name = self.__nameFor(mod, p.getName())
+ val = data[name] if data.has_key(name) else p.getDefValue()
+ grp.append( { 'name' : name, 'gui' : p.guiField(val).guiDescription(), 'value' : val } )
+ res.append( {'moduleLabel': v['friendlyName'], 'prefs': grp} )
+ return res
+
+
+ def processRequestForUserPreferences(self, user, data):
+ '''
+ Returns a list of errors in case of error, else return None
+ '''
+ # First, read fields form every single "section"
+ logger.debug('Processing {0}'.format(self._prefs))
+ prefs = []
+ for mod, v in self._prefs.iteritems():
+ logger.debug(mod)
+ form = forms.Form(data)
+ for p in v['prefs']:
+ name = self.__nameFor(mod, p.getName())
+ form.fields[name] = p.formField(None)
+ if form.is_valid() is False:
+ logger.debug("errors")
+ return form.errors
+ for p in v['prefs']:
+ name = self.__nameFor(mod, p.getName())
+ logger.debug(name)
+ prefs.append({ 'module': mod, 'name': p.getName(), 'value': form.cleaned_data[name] } )
+ user.preferences.all().delete()
+ for p in prefs:
+ user.preferences.create(module=p['module'], name=p['name'], value=p['value'])
+ return None
+
+ def processGuiForUserPreferences(self, user, data):
+ '''
+ '''
+ logger.debug('Processing data {0}'.format(data))
+ prefs = []
+ for mod, v in self._prefs.iteritems():
+ logger.debug(mod)
+ for p in v['prefs']:
+ name = self.__nameFor(mod, p.getName())
+ if data.has_key(name):
+ prefs.append( { 'module': mod, 'name': p.getName(), 'value': data[name] } )
+ user.preferences.all().delete()
+ for p in prefs:
+ user.preferences.create(module=p['module'], name=p['name'], value=p['value'])
+
+
+
+class UserPreference(object):
+ TYPE = 'abstract'
+ def __init__(self, **kwargs):
+ self._name = kwargs['name']
+ self._label = kwargs['label']
+ self._defValue = kwargs['defvalue'] if kwargs.has_key('defvalue') else None
+
+ def getName(self):
+ return self._name
+
+ def getDefValue(self):
+ return self._defValue
+
+ def formField(self, value):
+ '''
+ Returns a form field to add to the preferences form
+ '''
+ raise NameError('Can\'t create an abstract preference!!!')
+
+ def guiField(self):
+ '''
+ '''
+ raise NameError('Can\'t create an abstract preference!!!')
+
+
+class UserTextPreference(UserPreference):
+ TYPE = 'text'
+ def __init__(self, **kwargs):
+ super(self.__class__,self).__init__(**kwargs)
+ self._length = kwargs['length'] if kwargs.has_key('length') else None
+
+ def formField(self, value):
+ return forms.CharField(label = _(self._label), initial = value)
+
+
+class UserNumericPreference(UserPreference):
+ TYPE = 'numeric'
+ def __init__(self, **kwargs):
+ super(self.__class__,self).__init__(**kwargs)
+ self._min = kwargs['minvalue'] if kwargs.has_key('minvalue') else None
+ self._max = kwargs['maxvalue'] if kwargs.has_key('maxvalue') else None
+
+ def formField(self, value):
+ return forms.IntegerField(label = _(self._label), initial = value, min_value = self._min, max_value = self._max)
+
+class UserChoicePreference(UserPreference):
+ TYPE = 'choice'
+ def __init__(self, **kwargs):
+ super(self.__class__,self).__init__(**kwargs)
+ '''
+ Values are a tuple of
+ '''
+ self._values = kwargs['values']
+
+ def formField(self, value):
+ return forms.ChoiceField(label = _(self._label), initial = value, choices = self._values)
+
+ def guiField(self, value):
+ vals = []
+ for v in self._values:
+ vals.append( { 'id': v[0], 'text': _(v[1]) } )
+ return gui.ChoiceField(label = _(self._label), rdonly = False, values = vals, defvalue=value, tooltip = _(self._label))
+
+class UserCheckboxPreference(UserPreference):
+ TYPE = 'checkbox'
+ def __init__(self, **kwargs):
+ super(self.__class__,self).__init__(**kwargs)
+
+
+class CommonPrefs(object):
+ SZ_PREF = 'screenSize'
+ SZ_640x480 = '1'
+ SZ_800x600 = '2'
+ SZ_1024x768 = '3'
+ SZ_FULLSCREEN = 'F'
+
+ DEPTH_PREF = 'screenDepth'
+ DEPTH_8 = '1'
+ DEPTH_16 = '2'
+ DEPTH_24 = '3'
+ DEPTH_32 = '4'
+
+ @staticmethod
+ def getWidthHeight(prefsDict):
+ '''
+ Get width based on screenSizePref value
+ '''
+ return { CommonPrefs.SZ_640x480 : (640, 480),
+ CommonPrefs.SZ_800x600 : (800, 600),
+ CommonPrefs.SZ_1024x768 : (1024, 768),
+ CommonPrefs.SZ_FULLSCREEN : (-1, -1)
+ }[prefsDict[CommonPrefs.SZ_PREF]]
+
+ @staticmethod
+ def getDepth(prefsDict):
+ '''
+ Get depth based on depthPref value
+ '''
+ return { CommonPrefs.DEPTH_8 : 8,
+ CommonPrefs.DEPTH_16 : 16,
+ CommonPrefs.DEPTH_24 : 24,
+ CommonPrefs.DEPTH_32 : 32 }[ prefsDict[CommonPrefs.DEPTH_PREF] ]
+
+
+
+
+ screenSizePref = UserChoicePreference(name = SZ_PREF, label = ugettext_lazy('Screen Size'), defvalue = SZ_FULLSCREEN, values = (
+ (SZ_640x480, '640x480'),
+ (SZ_800x600, '800x600'),
+ (SZ_1024x768, '1024x768'),
+ (SZ_FULLSCREEN, ugettext_lazy('Full Screen'))
+ )
+ )
+ depthPref = UserChoicePreference(name = DEPTH_PREF, label = ugettext_lazy('Screen colors'), defvalue = DEPTH_24, values = (
+ (DEPTH_8, ugettext_lazy('8 bits')),
+ (DEPTH_16, ugettext_lazy('16 bits')),
+ (DEPTH_24, ugettext_lazy('24 bits')),
+ (DEPTH_32, ugettext_lazy('32 bits')),
+ )
+ )
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/managers/UserServiceManager.py b/trunk/server/src/uds/core/managers/UserServiceManager.py
new file mode 100644
index 00000000..6f1dc14c
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/UserServiceManager.py
@@ -0,0 +1,456 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from django.db.models import Q
+from django.db import transaction
+from uds.core.jobs.DelayedTask import DelayedTask
+from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
+from uds.core.services.Exceptions import OperationException
+from uds.core.util.State import State
+from uds.core.util.Config import GlobalConfig
+from uds.core.services.Exceptions import MaxServicesReachedException
+from uds.models import UserService, getSqlDatetime
+from uds.core import services
+from uds.core.services import Service
+import logging
+
+logger = logging.getLogger(__name__)
+
+USERSERVICE_TAG = 'cm-'
+
+class UserServiceOpChecker(DelayedTask):
+ def __init__(self, cache):
+ super(UserServiceOpChecker,self).__init__()
+ self._svrId = cache.id
+ self._state = cache.state
+
+ @staticmethod
+ def makeUnique(userService, userServiceInstance, state):
+ '''
+ This method makes sure that there will be only one delayedtask related to the userService indicated
+ '''
+ DelayedTaskRunner.runner().remove(USERSERVICE_TAG + str(userService.id))
+ UserServiceOpChecker.checkAndUpdateState(userService, userServiceInstance, state)
+
+ @staticmethod
+ def checkAndUpdateState(userService, userServiceInstance, state):
+ '''
+ Checks the value returned from invocation to publish or checkPublishingState, updating the dsp database object
+ Return True if it has to continue checking, False if finished
+ '''
+ prevState = userService.state
+ userService.unique_id = userServiceInstance.getUniqueId() # Updates uniqueId
+ userService.friendly_name = userServiceInstance.getName() # And name, both methods can modify serviceInstance, so we save it later
+ if State.isFinished(state):
+ checkLater = False
+ userServiceInstance.finish()
+ if State.isPreparing(prevState):
+ if userServiceInstance.service().publicationType is None or userService.publication == userService.deployed_service.activePublication():
+ userService.setState(State.USABLE)
+ # and make this usable if os manager says that it is usable, else it pass to configuring state
+ if userServiceInstance.osmanager() is not None and userService.os_state == State.PREPARING: # If state is already "Usable", do not recheck it
+ stateOs = userServiceInstance.osmanager().checkState(userService)
+ # If state is finish, we need to notify the userService again that os has finished
+ if State.isFinished(stateOs):
+ state = userServiceInstance.notifyReadyFromOsManager('')
+ userService.updateData(userServiceInstance)
+ else:
+ stateOs = State.FINISHED
+
+ if State.isRuning(stateOs):
+ userService.setOsState(State.PREPARING)
+ else:
+ userService.setOsState(State.USABLE)
+ else:
+ # We ignore OsManager info and if userService don't belong to "current" publication, mark it as removable
+ userService.setState(State.REMOVABLE)
+ elif State.isRemoving(prevState):
+ if userServiceInstance.osmanager() is not None:
+ userServiceInstance.osmanager().release(userService)
+ userService.setState(State.REMOVED)
+ else:
+ # Canceled,
+ userService.setState(State.CANCELED)
+ userService.updateData(userServiceInstance)
+ elif State.isErrored(state):
+ checkLater = False
+ userService.updateData(userServiceInstance)
+ userService.setState(State.ERROR)
+ else:
+ checkLater = True # The task is running
+ userService.updateData(userServiceInstance)
+ userService.save()
+ if checkLater:
+ UserServiceOpChecker.checkLater(userService, userServiceInstance)
+
+ @staticmethod
+ def checkLater(userService, ci):
+ '''
+ Inserts a task in the delayedTaskRunner so we can check the state of this publication
+ @param dps: Database object for DeployedServicePublication
+ @param pi: Instance of Publication manager for the object
+ '''
+ # Do not add task if already exists one that updates this service
+ if DelayedTaskRunner.runner().checkExists(USERSERVICE_TAG + str(userService.id)):
+ return
+ DelayedTaskRunner.runner().insert(UserServiceOpChecker(userService), ci.suggestedTime, USERSERVICE_TAG + str(userService.id))
+
+
+ @transaction.commit_manually
+ def run(self):
+ logger.debug('Checking user service finished {0}'.format(self._svrId))
+ try:
+ uService = UserService.objects.select_for_update().get(pk=self._svrId)
+ if uService.state != self._state:
+ logger.debug('Task overrided by another task (state of item changed)')
+ # This item is no longer valid, returning will not check it again (no checkLater called)
+ transaction.rollback()
+ return
+
+ ci = uService.getInstance()
+ logger.debug("uService instance class: {0}".format(ci.__class__))
+ state = ci.checkState()
+ UserServiceOpChecker.checkAndUpdateState(uService, ci, state)
+ except UserService.DoesNotExist, e:
+ logger.error('User service not found (erased from database?) {0} : {1}'.format(e.__class__, e))
+ except Exception, e:
+ # Exception caught, mark service as errored
+ logger.exception("Error {0}, {1} :".format(e.__class__, e))
+ try:
+ uService.setState(State.ERROR)
+ uService.save()
+ except Exception:
+ logger.error('Can\'t update state of uService object')
+ transaction.commit()
+
+
+class UserServiceManager(object):
+ _manager = None
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def manager():
+ if UserServiceManager._manager == None:
+ UserServiceManager._manager = UserServiceManager()
+ return UserServiceManager._manager
+
+ @staticmethod
+ def getCacheStateFilter(level):
+ return Q(cache_level=level) & UserServiceManager.getStateFilter()
+
+ @staticmethod
+ def getStateFilter():
+ return Q(state__in=[State.PREPARING, State.USABLE])
+
+
+ @transaction.commit_on_success
+ def __checkMaxDeployedReached(self, deployedService):
+ '''
+ Checks if maxDeployed for the service has been reached, and, if so,
+ raises an exception that no more services of this kind can be reached
+ '''
+ serviceInstance = deployedService.service.getInstance()
+ # Early return, so no database count is needed
+ if serviceInstance.maxDeployed == Service.UNLIMITED:
+ return
+
+ numberOfServices = deployedService.userServices.select_for_update().filter(
+ state__in=[State.PREPARING, State.USABLE]).count()
+ if serviceInstance.maxDeployed <= numberOfServices:
+ raise MaxServicesReachedException(
+ 'Max number of allowed deployments for service reached'
+ )
+
+
+ def __createCacheAtDb(self, deployedServicePublication, cacheLevel):
+ '''
+ Private method to instatiate a cache element at database with default states
+ '''
+ # Checks if maxDeployed has been reached and if so, raises an exception
+ self.__checkMaxDeployedReached(deployedServicePublication.deployed_service)
+ now = getSqlDatetime()
+ return deployedServicePublication.userServices.create(cache_level = cacheLevel, state = State.PREPARING, os_state = State.PREPARING,
+ state_date=now, creation_date=now, data = '', deployed_service = deployedServicePublication.deployed_service,
+ user = None, in_use = False )
+
+ def __createAssignedAtDb(self, deployedServicePublication, user):
+ '''
+ Private method to instatiate an assigned element at database with default state
+ '''
+ self.__checkMaxDeployedReached(deployedServicePublication.deployed_service)
+ now = getSqlDatetime()
+ return deployedServicePublication.userServices.create(cache_level=0, state=State.PREPARING, os_state=State.PREPARING,
+ state_date=now, creation_date=now, data='', deployed_service=deployedServicePublication.deployed_service, user=user, in_use=False)
+
+ def __createAssignedAtDbForNoPublication(self, deployedService, user):
+ '''
+ __createCacheAtDb and __createAssignedAtDb uses a publication for create the UserService.
+ There is cases where deployed services do not have publications (do not need them), so we need this method to create
+ an UserService with no publications, and create them from an DeployedService
+ '''
+ self.__checkMaxDeployedReached(deployedService)
+ now = getSqlDatetime()
+ return deployedService.userServices.create(cache_level=0, state=State.PREPARING, os_state=State.PREPARING,
+ state_date=now, creation_date=now, data='', publication=None, user=user, in_use=False)
+
+
+ @transaction.commit_on_success
+ def createCacheFor(self, deployedService, cacheLevel):
+ '''
+ Creates a new cache for the deployed service publication at level indicated
+ '''
+ logger.debug('Creating a new cache element at level {0} for publication {1}'.format(cacheLevel, deployedService))
+ cache = self.__createCacheAtDb(deployedService, cacheLevel)
+ ci = cache.getInstance()
+ state = ci.deployForCache(cacheLevel)
+
+ UserServiceOpChecker.checkAndUpdateState(cache, ci, state)
+ return cache
+
+ @transaction.commit_on_success
+ def createAssignedFor(self, ds, user):
+ '''
+ Creates a new assigned deployed service for the publication and user indicated
+ '''
+ if ds.service.getType().publicationType is not None:
+ dsp = ds.activePublication()
+ logger.debug('Creating a new assigned element for user {0} por publication {1}'.format(user, dsp))
+ assigned = self.__createAssignedAtDb(dsp, user)
+ else:
+ logger.debug('Creating a new assigned element for user {0}'.format(user))
+ assigned = self.__createAssignedAtDbForNoPublication(ds, user)
+
+ ai = assigned.getInstance()
+ state = ai.deployForUser(user)
+
+ UserServiceOpChecker.makeUnique(assigned, ai, state)
+
+ return assigned
+
+ @transaction.commit_on_success
+ def createAssignable(self, ds, deployed, user):
+ '''
+ Creates an assignable service
+ '''
+ now = getSqlDatetime()
+ assignable = ds.userServices.create(cache_level=0, state=State.PREPARING, os_state=State.PREPARING,
+ state_date=now, creation_date=now, data='', user=user, in_use=False)
+ state = deployed.deployForUser(user)
+ try:
+ UserServiceOpChecker.makeUnique(assignable, deployed, state)
+ except Exception, e:
+ logger.exception("Exception {0}".format(e))
+ logger.debug("Assignable: {0}".format(assignable))
+ return assignable
+
+
+
+ @transaction.commit_on_success
+ def moveToLevel(self, cache, cacheLevel):
+ '''
+ Moves a cache element from one level to another
+ @return: cache element
+ '''
+ cache = UserService.objects.select_for_update().get(id=cache.id)
+ logger.debug('Moving cache {0} to level {1}'.format(cache, cacheLevel))
+ ci = cache.getInstance()
+ state = ci.moveToCache(cacheLevel)
+ cache.cache_level = cacheLevel
+ if State.isRuning(state) and cache.isUsable():
+ cache.setState(State.PREPARING)
+
+ UserServiceOpChecker.makeUnique(cache, ci, state)
+ transaction.commit()
+
+ @transaction.commit_on_success
+ def cancel(self, uService):
+ '''
+ Cancels a user service creation
+ @return: the Uservice canceling
+ '''
+ uService = UserService.objects.select_for_update().get(id=uService.id)
+ logger.debug('Canceling uService {0} creation'.format(uService))
+ if uService.isPreparing() == False:
+ raise OperationException(_('Can\'t cancel non running operation'))
+ ui = uService.getInstance()
+ # We simply notify service that it should cancel operation
+ state = ui.cancel()
+ uService.updateData(ui)
+ uService.setState(State.CANCELING)
+ UserServiceOpChecker.makeUnique(uService, ui, state)
+ return uService
+
+
+ @transaction.commit_on_success
+ def remove(self, uService):
+ '''
+ Removes a uService element
+ @return: the uService removed (marked for removal)
+ '''
+ uService = UserService.objects.select_for_update().get(id=uService.id)
+ logger.debug('Removing uService {0}'.format(uService))
+ if uService.isUsable() == False and State.isRemovable(uService.state) == False:
+ raise OperationException(_('Can\'t remove a non active element'))
+
+ ci = uService.getInstance()
+ state = ci.destroy()
+ uService.setState(State.REMOVING)
+ UserServiceOpChecker.makeUnique(uService, ci, state)
+
+ def removeOrCancel(self, uService):
+ if uService.isUsable() or State.isRemovable(uService.state):
+ return self.remove(uService)
+ elif uService.isPreparing():
+ return self.cancel(uService)
+ else:
+ raise OperationException(_('Can\'t remove nor cancel {0} cause its states doesn\'t allows it'))
+
+ @transaction.commit_on_success
+ def removeInfoItems(self, dsp):
+ dsp.cachedDeployedService.select_for_update().filter(state__in=State.INFO_STATES).delete()
+
+
+ @transaction.commit_on_success
+ def getAssignationForUser(self, ds, user):
+ # First, we try to locate an already assigned service
+ existing = ds.assignedUserServices().filter(user=user,state__in=State.VALID_STATES)
+ lenExisting = existing.count()
+ if lenExisting > 0: # Already has 1 assigned
+ logger.debug('Found assigned service from {0} to user {1}'.format(ds, user.name))
+ if existing[0].state == State.ERROR:
+ if lenExisting > 1:
+ return existing[1]
+ else:
+ return existing[0]
+
+ # Now try to locate 1 from cache already "ready" (must be usable and at level 1)
+ cache = ds.cachedUserServices().select_for_update().filter(cache_level = services.UserDeployment.L1_CACHE, state = State.USABLE)[:1]
+ if len(cache) > 0:
+ cache = cache[0] # Database object
+ cache.assignToUser(user)
+ cache.save() # Store assigned ASAP, we do not know how long assignToUser method of instance will take
+ logger.debug('Found a cached-ready service from {0} for user {1}, item {2}'.format(ds, user, cache))
+ ci = cache.getInstance() # User Deployment instance
+ ci.assignToUser(user)
+ cache.updateData(ci)
+ cache.save()
+ return cache
+ # Now find if there is a preparing one
+ cache = ds.cachedUserServices().select_for_update().filter(cache_level = services.UserDeployment.L1_CACHE, state = State.PREPARING)[:1]
+ if len(cache) > 0:
+ cache = cache[0]
+ cache.assignToUser(user)
+ cache.save()
+ logger.debug('Found a cached-preparing service from {0} for user {1}, item {2}'.format(ds, user, cache))
+ cache.getInstance().assignToUser(user)
+ return cache
+ # Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
+ ty = ds.service.getType()
+ if ty.usesCache is True:
+ inCacheL1 = ds.cachedUserServices().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L1_CACHE)).count()
+ inAssigned = ds.assignedUserServices().filter(UserServiceManager.getStateFilter()).count()
+ totalL1Assigned = inCacheL1 + inAssigned
+ if totalL1Assigned >= ds.max_srvs:
+ raise MaxServicesReachedException()
+ # Can create new service, create it
+ return self.createAssignedFor(ds, user)
+
+ def getServicesInStateForProvider(self, provider_id, state):
+ '''
+ Returns the number of services of a service provider in the state indicated
+ '''
+ return UserService.objects.filter(deployed_service__service__provider__id=provider_id, state=state).count()
+
+ def canRemoveServiceFromDeployedService(self, ds):
+ '''
+ checks if we can do a "remove" from a deployed service
+ '''
+ removing = self.getServicesInStateForProvider(ds.service.provider_id, State.REMOVING)
+ if removing >= GlobalConfig.MAX_REMOVING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() == False:
+ return False
+ return True
+
+ def canInitiateServiceFromDeployedService(self, ds):
+ '''
+ Checks if we can start a new service
+ '''
+ preparing = self.getServicesInStateForProvider(ds.service.provider_id, State.PREPARING)
+ if preparing >= GlobalConfig.MAX_PREPARING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() == False:
+ return False
+ return True
+
+ @transaction.commit_on_success
+ def isReady(self, uService):
+ UserService.objects.update()
+ uService = UserService.objects.select_for_update().get(id=uService.id)
+ logger.debug('Checking ready of {0}'.format(uService))
+ if uService.state != State.USABLE or uService.os_state != State.USABLE:
+ logger.debug('State is not usable for {0}'.format(uService))
+ return False
+ logger.debug('Service {0} is usable, checking it via setReady'.format(uService))
+ ui = uService.getInstance()
+ state = ui.setReady()
+ logger.debug('State: {0}'.format(state))
+ uService.updateData(ui)
+ if state == State.FINISHED:
+ uService.save()
+ return True
+ uService.setState(State.PREPARING)
+ UserServiceOpChecker.makeUnique(uService, ui, state)
+ return False
+
+ @transaction.commit_on_success
+ def checkForRemoval(self, uService):
+ uService = UserService.objects.select_for_update().get(id=uService.id)
+ if uService.publication == None:
+ return
+ if uService.publication.id != uService.deployed_service.activePublication().id:
+ logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
+ uService.setState(State.REMOVABLE)
+
+
+ def notifyReadyFromOsManager(self, uService, data):
+ ui = uService.getInstance()
+ logger.debug('Notifying user service ready state')
+ state = ui.notifyReadyFromOsManager(data)
+ logger.debug('State: {0}'.format(state))
+ uService.updateData(ui)
+ if state == State.FINISHED:
+ uService.save()
+ elif uService.state in (State.USABLE, State.PREPARING): # We don't want to get active deleting or deleted machines...
+ uService.setState(State.PREPARING)
+ UserServiceOpChecker.makeUnique(uService, ui, state)
+
diff --git a/trunk/server/src/uds/core/managers/__init__.py b/trunk/server/src/uds/core/managers/__init__.py
new file mode 100644
index 00000000..18566a1f
--- /dev/null
+++ b/trunk/server/src/uds/core/managers/__init__.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS managers (downloads, users preferences, publications, ...)
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+def cryptoManager():
+ from CryptoManager import CryptoManager
+ return CryptoManager.manager()
+
+
+def taskManager():
+ from TaskManager import TaskManager
+ return TaskManager
+
+
+def downloadsManager():
+ from DownloadsManager import DownloadsManager
+ return DownloadsManager.manager()
+
+def logManager():
+ from LogManager import LogManager
+ return LogManager.manager()
diff --git a/trunk/server/src/uds/core/osmanagers/BaseOsManager.py b/trunk/server/src/uds/core/osmanagers/BaseOsManager.py
new file mode 100644
index 00000000..deea8488
--- /dev/null
+++ b/trunk/server/src/uds/core/osmanagers/BaseOsManager.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.util.State import State
+from uds.core import Module
+
+STORAGE_KEY = 'osmk'
+
+class OSManager(Module):
+ '''
+ An OS Manager is responsible for communication the service the different actions to take (i.e. adding a windows machine to a domain)
+ The Service (i.e. virtual machine) communicates with the OSManager via a published web method, that must include the unique ID.
+ In order to make easier to agents identify themselfs, the Unique ID can be a list with various Ids (i.e. the macs of the virtual machine).
+ Server will iterate thought them and look for an identifier associated with the service. This list is a comma separated values (i.e. AA:BB:CC:DD:EE:FF,00:11:22:...)
+ Remember also that we inherit the test and check methods from BaseModule
+ '''
+ # Service informational related data
+ typeName = _('Base OS Manager')
+ typeType = 'BaseOSManager'
+ typeDescription = _('Base Manager')
+ iconFile = 'osmanager.png'
+
+ # If true, this os manager will be invoked with every user service assigned, but not used from time to time
+ # Time is defined as a global config
+ processUnusedMachines = False
+
+ def __init__(self,environment, values):
+ super(OSManager, self).__init__(environment, values)
+ self.initialize(values)
+
+ def initialize(self, values):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base methods.
+ This will get invoked when all initialization stuff is done
+
+ Args:
+ Values: If values is not none, this object is being initialized
+ from administration interface, and not unmarshal will be done.
+ If it's None, this is initialized internally, and unmarshal will
+ be called after this.
+
+ Default implementation does nothing
+ '''
+ pass
+
+ def release(self, service):
+ '''
+ Called by a service that is in Usable state before destroying it so osmanager can release data associated with it
+ Only invoked for services that reach the state "removed"
+ @return nothing
+ '''
+ pass
+
+ # These methods must be overriden
+ def process(self,service, message, data):
+ '''
+ This method must be overriden so your so manager can manage requests and responses from agent.
+ @param service: Service that sends the request (virtual machine or whatever)
+ @param message: message to process (os manager dependent)
+ @param data: Data for this message
+ '''
+ pass
+
+ def checkState(self,service):
+ '''
+ This method must be overriden so your os manager can respond to requests from system to the current state of the service
+ This method will be invoked when:
+ * After service creation has finished, with the service wanting to see if it has to wait for os manager process finalization
+ * After call to process method, to check if the state has changed
+ * Before assigning a service to an user (maybe this is not needed)?
+ Notice that the service could be in any state. In fact, what we want with this is return FINISHED if nothing is expected from os o RUNING else
+ The state will be updated by actors inside oss, so no more direct checking is needed
+ @return: RUNNING, FINISHED
+ We do not expect any exception from this method
+ '''
+ return State.FINISHED
+
+
+ def processUnused(self, userService):
+ '''
+ This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME
+ This function can update userService values. Normal operation will be remove machines if this state is not valid
+ '''
+ pass
+
+ @classmethod
+ def transformsUserOrPasswordForService(cls):
+ '''
+ Helper method that informs if the os manager transforms the username and/or the password.
+ This is used from DeployedService
+ '''
+ return cls.processUserPassword != OSManager.processUserPassword
+
+ def processUserPassword(self, service, username, password):
+ '''
+ This will be invoked prior to passsing username/password to Transport.
+
+ This method allows us to "change" username and/or password "on the fly".
+ One example of use of this is an OS Manager that creates a random password for an user.
+ In that case, this method, if the username passed in is the same as the os manager changes the password for, return the changed password.
+
+ MUST Return:
+ An array with 2 elements, [newUserName, newPassword].
+ Default method simplt does nothing with in parameters, just returns it. (So, if your os manager does not need this,
+ simply do not implement it)
+
+ Note: This method is, right now, invoked by Transports directly. So if you implement a Transport, remember to invoke this
+ '''
+ return [username, password]
+
+ def destroy(self):
+ '''
+ Invoked when OS Manager is deleted
+ '''
+ pass
+
+ def __str__(self):
+ return "Base OS Manager"
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/osmanagers/OSManagersFactory.py b/trunk/server/src/uds/core/osmanagers/OSManagersFactory.py
new file mode 100644
index 00000000..2a9067d2
--- /dev/null
+++ b/trunk/server/src/uds/core/osmanagers/OSManagersFactory.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class OSManagersFactory(object):
+ _factory = None
+
+ def __init__(self):
+ self._jobs = {}
+
+ @staticmethod
+ def factory():
+ if OSManagersFactory._factory == None:
+ OSManagersFactory._factory = OSManagersFactory()
+ return OSManagersFactory._factory
+
+ def providers(self):
+ return self._jobs
+
+ def insert(self, type):
+ self._jobs[type.type()] = type
+
+ def lookup(self, typeName):
+ try:
+ return self._jobs[typeName]
+ except KeyError:
+ return None
diff --git a/trunk/server/src/uds/core/osmanagers/__init__.py b/trunk/server/src/uds/core/osmanagers/__init__.py
new file mode 100644
index 00000000..30d8daf4
--- /dev/null
+++ b/trunk/server/src/uds/core/osmanagers/__init__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS os managers related interfaces and classes
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from BaseOsManager import OSManager
+
+def factory():
+ '''
+ Returns factory for register/access to authenticators
+ '''
+ from OSManagersFactory import OSManagersFactory
+ return OSManagersFactory.factory()
+
diff --git a/trunk/server/src/uds/core/osmanagers/osmanager.png b/trunk/server/src/uds/core/osmanagers/osmanager.png
new file mode 100644
index 00000000..a11c034d
Binary files /dev/null and b/trunk/server/src/uds/core/osmanagers/osmanager.png differ
diff --git a/trunk/server/src/uds/core/services/BaseDeployed.py b/trunk/server/src/uds/core/services/BaseDeployed.py
new file mode 100644
index 00000000..ae402ae5
--- /dev/null
+++ b/trunk/server/src/uds/core/services/BaseDeployed.py
@@ -0,0 +1,572 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core import Environmentable
+from uds.core import Serializable
+from uds.core.util.State import State
+
+class UserDeployment(Environmentable, Serializable):
+ '''
+ Interface for deployed services.
+
+ This class provides the needed logic for implementing an "consumable user service",
+ that are the elements that the user will interact with.
+
+ A good way to understand this class is to look at the sample services provided
+ with the documentation.
+
+ As with all modules interfaces, if you override __init__ method,
+ do not forget to invoke this class __init__ method as this::
+
+ super(self.__class__, self).__init__(environment, **kwargs)
+
+ This is a MUST (if you override __init___), so internal structured gets filled correctly, so don't forget it!.
+
+ The preferred way of initializing, is to provide :py:meth:`.initialize`, that
+ will be invoked just after all initialization stuff is done at __init__.
+
+ Normally objects of classes deriving from this one, will be serialized, called,
+ deserialized. This means that all that you want to ensure that is keeped inside
+ the class must be serialized and unserialized, because there is no warantee that
+ the object will get two methods invoked without haven't been remoded from memory
+ and loaded again, this means, IMPLEMENT marshal and unmarshal with all attributes
+ that you want to keep.
+
+
+ Things to know about this class:
+
+ * Once a deployment is done, it will never be called again for same instance
+ object
+ * The method getUniqueId will be invoked after call to deploys and check.
+ You can change it on the fly, but remember that uniqueId is the "keyword"
+ used inside services to communicate with os managers (os manager will
+ receive an instance of UserDeployment, and this will be located via that
+ uniqueId)
+
+ Uniques ids can be repeated at database level, to let it come at a later
+ deployment stage, but if two services has same uniqueid at a time,
+ os manager will simply not work.
+ * suggestedTime is always accessed through instance objects, and used after
+ deployForCache, deployForUser and moveToCache it these methods returns
+ RUNNING
+ * Checks (if a deployment has finished, or the cache movement is finished)
+ are always done using checkState(). It is secuential, i mean, will only
+ be called when a deployment,a cache movement or a cancel operation is
+ running
+ * If the service that supports this deployeds do not use L2 cache, the
+ moveCache method will never be invoked
+ * The L1 cache should be a fast access cache (i.e. running service but
+ not assigned to an user), while L2 cache should be non consuming or
+ almost-non-consuming service. This means that if we cannont make an
+ slower an less resources consumable form for a service, this should
+ not have an L2 cache (slower is not a must,
+ but probably it will be slower to recover from L2 cache than from L1,
+ but faster than creating a new service)
+ Ofc, if a service has an "Instant" creation, it don't needs cache...
+ * We do not expect any exception from these methods, but if there is an
+ error, the method can return "ERROR". To show the reason of error, the
+ method reasonOfError can be called multiple times, including
+ serializations in middle, so remember to include reason of error in serializations
+ '''
+ L1_CACHE = 1 #: Constant for Cache of level 1
+ L2_CACHE = 2 #: Constant for Cache of level 2
+
+ #: Suggested time for deployment finishing, in seconds
+ #: This allows the manager to, if deployment is no done in 1 step, re-check
+ #: the deployment once this time has passed, i.e. KVM COW deployment takes
+ #: low time, so we suggest to check at short intervals, but full copys takes
+ #: a bit more so we can use longer interval checks
+ #: This attribute is accessed always through an instance object,
+ #: so u can modify it at your own implementation.
+ suggestedTime = 10
+
+ def __init__(self, environment, **kwargs):
+ '''
+ Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, **kwargs)"
+ We want to use the env, cache and storage methods outside class. If not called, you must implement your own methods
+ cache and storage are "convenient" methods to access _env.cache() and _env.storage()
+
+ Invoking this from derived classes is a MUST, again, do not forget it or your
+ module will probable never work.
+
+ Args:
+
+ environment: Environment assigned to this publication
+ kwargs: List of arguments that will receive:
+ service: Parent service (derived from Service) of this deployment (this is an instance, not database object)
+ publication: Parent publication (derived from Publication) of this deployment (optional)(this is an instance, not database object)
+ osmanager: Parent osmanager (derived from :py:class:`uds.core.osmanagersOSManager`) of this deployment (optional)(this is an instance, not database object)
+ dbservice: Database object for this service
+ '''
+ Environmentable.__init__(self, environment)
+ Serializable.__init__(self)
+ self._service = kwargs['service'] # Raises an exception if service is not included. Parent
+ if kwargs.has_key('publication'):
+ self._publication = kwargs['publication']
+ else:
+ self._publication = None
+ if kwargs.has_key('osmanager'):
+ self._osmanager = kwargs['osmanager']
+ else:
+ self._osmanager = None
+ if kwargs.has_key('dbservice'): # Reference to database service, will be there most time :-)
+ self._dbService = kwargs['dbservice']
+ else:
+ self._dbService = None
+
+ self.initialize()
+
+ def initialize(self):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base class __init__.
+ This will get invoked when all initialization stuff is done, so
+ you can here access publication, service, osManager, ...
+ '''
+ pass
+
+
+ def getName(self):
+ '''
+ Override this to return a name to display under some circustances
+
+ Returns:
+
+ name, default implementation returns unique id
+ '''
+ return self.getUniqueId()
+
+ def service(self):
+ '''
+ Utility method to access parent service. This doesn't need to be override.
+
+ Normaly user deployments will need parent service to provide the
+ consumable to the user.
+
+ Returns:
+
+ Parent service of this User Deployment
+ '''
+ return self._service
+
+ def publication(self):
+ '''
+ Utility method to access publication. This doesn't need to be overriden.
+
+ Returns:
+
+ publication for this user deployment, or None if this deployment has
+ no publication at all.
+ '''
+ return self._publication
+
+ def osmanager(self):
+ '''
+ Utility method to access os manager. This doesn't need to be overriden.
+
+ Returns:
+
+ os manager for this user deployment, or None if this deployment has
+ no os manager.
+ '''
+ return self._osmanager
+
+ def dbservice(self):
+ '''
+ Utility method to access database object for the object this represents.
+
+ Returns:
+
+ Database object that got unserialized to obtain this object.
+ '''
+ return self._dbService
+
+ def macGenerator(self):
+ '''
+ Utility method to access provided macs generator (inside environment)
+
+ Returns the environment unique mac addresses generator
+ '''
+ return self.idGenerators('mac')
+
+ def nameGenerator(self):
+ '''
+ Utility method to access provided names generator (inside environment)
+
+ Returns the environment unique name generator
+ '''
+ return self.idGenerators('name')
+
+ def getUniqueId(self):
+ '''
+ Obtains an unique id for this deployed service, you MUST override this
+
+ Returns:
+
+ An unique identifier for this object, that is an string and must be
+ unique.
+ '''
+ raise Exception('Base getUniqueId for User Deployment called!!!')
+
+ def notifyReadyFromOsManager(self, data):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This method provides a mechanism to let os managers notify readyness
+ to deployed services.
+
+ Args:
+
+ Data: Data sent by os manager.
+ Data is os manager dependant, so check if this data is known by you
+ (normally, it will be None, but is os manager dependad as i say)
+
+ This is a task-initiating method, so if there is something to do,
+ just return State.RUNNING. If not, return State.FINISHED. In case of
+ error, return State.ERROR and be ready to provide error message when
+
+ if State.RUNNING is returned, the :py:meth:.checkState method will be
+ used to check when this process has finished.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ return State.FINISHED
+
+ def getIp(self):
+ '''
+ All services are "IP" services, so this method is a MUST
+
+ Returns:
+
+ The needed ip to let the user connect to the his deployed service.
+ This ip will be managed by transports, without IP there is no connection
+ '''
+ raise Exception('Base getIp for User Deployment got called!!!')
+
+ def setIp(self, ip):
+ '''
+ This is an utility method, invoked by some os manager to notify what they thinks is the ip for this service.
+ If you assign the service IP by your own methods, do not override this
+ '''
+ pass
+
+ def setReady(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+
+ This method exist for this kind of situations (i will explain it with a
+ sample)
+
+ Imagine a Service tree (Provider, Service, ...) for virtual machines.
+ This machines will get created by the UserDeployment implementation, but,
+ at some time, the machine can be put at in an state (suspend, shut down)
+ that will make the transport impossible to connect with it.
+
+ This method, in this case, will check the state of the machine, and if
+ it is "ready", that is, powered on and accessible, it will return
+ "State.FINISHED". If the machine is not accessible (has been erased, for
+ example), it will return "State.ERROR" and store a reason of error so UDS
+ can ask for it and present this information to the Administrator.
+
+ If the machine powered off, or suspended, or any other state that is not
+ directly usable but can be put in an usable state, it will return
+ "State.RUNNING", and core will use checkState to see when the operation
+ has finished.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ return State.FINISHED
+
+ def deployForCache(self, cacheLevel):
+ '''
+ Deploys a user deployment as cache.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The objective of this method is providing a cache copy of an user consumable,
+ and will be invoked whenever the core need to create a new copy for cache
+ of the service this UserDeployment manages.
+
+ Things to take care with this method are:
+
+ * cacheLevel can be L1 or L2 (class constants)
+ * If a deploy for cache is asked for a L1 cache, the generated
+ element is expected to be all-done for user consume. L1 cache items
+ will get directly assigned to users whenever needed, and are expected
+ to be fast. (You always have setReady method to do anything else needed
+ to assign the cache element to an user, but generally L1 cached items
+ must be ready to use.
+ * An L2 cache is expected to be an cached element that is "almost ready".
+ The main idea behind L2 is to keep some elements almost usable by users
+ but in an state that they do not consume (or consumes much less) resources.
+ If your L2 cache consumes the same that L1 cache, L2 cache is in fact not
+ needed.
+ * This works as :py:meth:.deployForUser, meaning that you can take a look
+ also to that method for more info
+
+ :note: If your service uses caching, this method MUST be provided. If it
+ do not uses cache, this method will never get called, so you can
+ skip it implementation
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('Base deploy for cache invoked! for class {0}'.format(self.__class__.__name__))
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The user parameter is not realy neded, but provided. It indicates the
+ Database User Object (see py:mod:`uds.modules`) to which this deployed
+ user service will be assigned to.
+
+ This method will get called whenever a new deployed service for an user
+ is needed. This will give this class the oportunity to create
+ a service that is assigned to an user.
+
+ The way of using this method is as follows:
+
+ If the service gets created in "one step", that is, before the return
+ of this method, the consumable service for the user gets created, it
+ will return "State.FINISH".
+ If the service needs more steps (as in this case), we will return
+ "State.RUNNING", and if it has an error, it wil return "State.ERROR" and
+ store an error string so administration interface can show it.
+
+ We do not use user for anything, as in most cases will be.
+
+ :note: override ALWAYS this method, or an exception will be raised
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('Base deploy for user invoked! for class {0}'.format(self.__class__.__name__))
+
+
+ def checkState(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+
+ If some of the initiating action tasks returns State.RUNNING. this method
+ will get called until it returns State.FINISH or State.ERROR.
+
+ In other words, whenever a multi step operation is initiated, this method
+ will get the responsability to check that the operation has finished or
+ failed. If the operation continues, but haven't finished yet, it must
+ return State.RUNNING. If has finished must return State.FINISH and if it
+ has some kind of error, State.ERROR and also store somewhere the info
+ that will be requested using :py:meth:.reasonOfError
+
+ :note: override ALWAYS this method, or an exception will be raised
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('Base check state invoked! for class {0}'.format(self.__class__.__name__))
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter whether it is for cache or for an user)
+
+ This gives the opportunity to make something at that moment.
+
+ Default implementation does nothing at all.
+
+ :note: You can also make these operations at checkState, this is really
+ not needed, but can be provided (default implementation of base class does
+ nothing)
+ '''
+ pass
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This is not a task method right now, simply a notification. This means
+ that L1 cache items must be directly usable (except for the readyness part)
+ by users in a single step operation.
+
+ Note that there will be an setReady call before letting the user consume
+ this user deployment, so this is more informational (so, if you keep at
+ what cache level is this instance, you can update it) than anything else.
+
+ This is not a task method. All level 1 cache items can be dircetly
+ assigned to an user with no more work needed, but, if something is needed,
+ here you can do whatever you need.
+
+ user is a Database user object.
+ '''
+ pass
+
+ def moveToCache(self, newLevel):
+ '''
+ This method is invoked whenever the core needs to move from the current
+ cache level to a new cache level an user deployment.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ We only provide newLevel, because there is only two cache levels, so if
+ newLevel is L1, the actual is L2, and if it is L2, the actual is L1.
+
+ Actually there is no possibility to move assigned services again back to
+ cache. If some service needs that kind of functionallity, this must be
+ provided at service level (for example, when doing publishing creating
+ a number of services that will be used, released and reused by users).
+
+ Also, user deployments that are at cache level 2 will never get directly
+ assigned to user. First, it will pass to L1 and then it will get assigned.
+
+ A good sample of a real implementation of this is moving a virtual machine
+ from a "suspended" state to "running" state to assign it to an user.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ return State.FINISHED
+
+ def userLoggedIn(self, username):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responsibility of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actors.
+ '''
+ pass
+
+ def userLoggedOut(self, username):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ pass
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+
+ :note: Remember that you can use ugettext to translate this error to
+ user language whenever it is possible. (This one will get invoked
+ directly from admin interface and, as so, will have translation
+ environment correctly set up.
+ '''
+ return 'unknown'
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This method gives the oportunity to remove associated data (virtual machine,
+ ...) for the user consumable this instance represents.
+
+ If return value is State.RUNNING, :py:meth:.checkState will be used to
+ check if the destroy operation has finished.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('destroy method for class {0} not provided!'.format(self.__class__.__name__))
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Cancel represents a canceling of the current running operation, and
+ can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('cancel method for class {0} not provided!'.format(self.__class__.__name__))
+
+ def __str__(self):
+ '''
+ Mainly used for debugging purposses
+ '''
+ return "Base Deployed Service"
diff --git a/trunk/server/src/uds/core/services/BasePublication.py b/trunk/server/src/uds/core/services/BasePublication.py
new file mode 100644
index 00000000..595078b9
--- /dev/null
+++ b/trunk/server/src/uds/core/services/BasePublication.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core import Environmentable
+from uds.core import Serializable
+
+class Publication(Environmentable, Serializable):
+ '''
+ This class is in fact an interface, and defines the logic of a publication
+ for a Service.
+
+ A publication is the preparation of the needs of a service before it can
+ be provided to users. One good sample of this is, in case of virtual machines,
+ to copy a machine to provide COWS of this copy to users.
+
+ As always, do not forget to invoke base class __init__ if you override it as this::
+
+ super(self.__class__, self).__init__(environment, **kwargs)
+
+ This is a MUST, so internal structured gets filled correctly, so don't forget it!.
+
+ The preferred method is not to override init, but provide the :py:meth:`.initialize`,
+ that will be invoked just after all internal initialization is completed.
+
+ Normally objects of classes deriving from this one, will be serialized, called,
+ deserialized. This means that all that you want to ensure that is keeped inside
+ the class must be serialized and unserialized, because there is no warantee that
+ the object will get two methods invoked without haven't been remoded from memory
+ and loaded again, this means, IMPLEMENT marshal and unmarshal with all attributes
+ that you want to keep.
+ '''
+
+ # Constants for publications
+
+ # Description of the publication
+
+ #:Suggested time for publication finishing, in seconds
+ #: This allows the manager to, if publication is no done in 1 step,
+ #: re-check the publication once this time has passed, i.e. KVM COW publication
+ #: takes low time, so we suggest to check at short intervals,
+ #: but full clone takes a lot, so we suggest that checks are done more steady.
+ #: This attribute is always accessed using an instance object, so you can
+ #: change suggestedTime in your implementation.
+ suggestedTime = 10
+
+ def __init__(self, environment, **kwargs):
+ '''
+ Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, values)"
+ We want to use the env, cache and storage methods outside class. If not called, you must implement your own methods
+ cache and storage are "convenient" methods to access _env.cache() and _env.storage()
+ @param environment: Environment assigned to this publication
+ '''
+ Environmentable.__init__(self, environment)
+ Serializable.__init__(self)
+ self._osManager = kwargs.get('osManager', None)
+ self._service = kwargs['service'] # Raises an exception if service is not included
+ self._revision = kwargs.get('revision', -1)
+ self._dsName = kwargs.get('dsName', 'Unknown')
+
+ self.initialize()
+
+ def initialize(self):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base class __init__.
+ This will get invoked when all initialization stuff is done, so
+ you can here access service, osManager, ...
+ '''
+ pass
+
+ def service(self):
+ '''
+ Utility method to access parent service of this publication
+
+ Returns
+
+ Parent service instance object (not database object)
+ '''
+ return self._service
+
+ def osManager(self):
+ '''
+ Utility method to access os manager for this publication.
+
+ Returns
+
+ Parent service instance object (not database object)
+ The returned value can be None if no Os manager is needed by
+ the service owner of this publication.
+ '''
+ return self._osManager
+
+ def revision(self):
+ '''
+ Utility method to access the revision of this publication
+ This is a numeric value, and is set by core
+ '''
+ return self._revision
+
+ def dsName(self):
+ '''
+ Utility method to access the declared deployed service name.
+
+ This name is set by core, using the administrator provided data
+ at administration interface.
+ '''
+ return self._dsName
+
+ def publish(self):
+ '''
+ This method is invoked whenever the administrator requests a new publication.
+
+ The method is not invoked directly (i mean, that the administration request
+ do no makes a call to this method), but a DelayedTask is saved witch will
+ initiate all publication stuff (and, of course, call this method).
+
+ You MUST implement it, so the publication do really something.
+ All publications can be synchronous or asynchronous.
+
+ The main difference between both is that first do whatever needed, (the
+ action must be fast enough to do not block core), returning State.FINISHED.
+
+ The second (asynchronous) are publications that could block the core, so
+ it have to be done in more than one step.
+
+ An example publication could be a copy of a virtual machine, where:
+ * First we invoke the copy operation to virtualization provider
+ * Second, we kept needed values inside instance so we can serialize
+ them whenever requested
+ * Returns an State.RUNNING, indicating the core that the publication
+ has started but has to finish sometime later. (We do no check
+ again the state and keep waiting here, because we will block the
+ core untill this operation is finished).
+
+ :note: This method MUST be provided, an exception is raised if not.
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('publish method for class {0} not provided! '.format(self.__class__.__name__))
+
+ def checkState(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This method will be invoked whenever a publication is started, but it
+ do not finish in 1 step.
+
+ The idea behind this is simple, we can initiate an operation of publishing,
+ that will be done at :py:meth:.publish method.
+
+ If this method returns that the operation has been initiated, but not finished
+ (State.RUNNING), the core will keep calling this method until checkState
+ returns State.FINISHED (or State.error).
+
+ You MUST always provide this method if you expect the publication no to be
+ done in 1 step (meaning this that if publish can return State.RUNNING, this
+ will get called)
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('checkState method for class {0} not provided!!!'.format(self.__class__.__name__))
+
+ def finish(self):
+ '''
+ Invoked when Publication manager noticed that the publication has finished.
+ This give us the opportunity of cleaning up things (as stored vars, etc..)
+ Returned value, if any, is ignored
+
+ Default implementation does nothing. You can leave default method if you
+ are going to do nothing.
+ '''
+ pass
+
+ def reasonOfError(self):
+ '''
+ If a publication produces an error, here we must return the reason why
+ it happened. This will be called just after publish or checkPublishingState
+ if they return State.ERROR
+
+ The returned value, an string, will be used always by administration interface,
+ meaning this that the translation environment will be ready, and that you
+ can use ugettext to return a version that can be translated to administration
+ interface language.
+ '''
+ return 'unknown'
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Invoked for destroying a deployed service
+ Do whatever needed here, as deleting associated data if needed
+ (i.e. a copy of the machine, snapshots, etc...)
+
+ This method MUST be provided, even if you do nothing here (in that case,
+ simply return State.FINISHED). Default implementation will raise an
+ exception if it gets called
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('destroy method for class {0} not provided!'.format(self.__class__.__name__))
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This method is invoked whenever the core needs a cancelation of current
+ operation. This will happen if we are, for example, preparing the
+ service for users, but the administration request to stop doing this.
+
+ This method MUST be provided, even if you do nothing here (in that case,
+ simply return State.FINISHED). Default implementation will raise an
+ exception if it gets called
+
+ :note: All task methods, like this one, are expected to handle
+ all exceptions, and never raise an exception from these methods
+ to the core. Take that into account and handle exceptions inside
+ this method.
+ '''
+ raise Exception('cancel method for class {0} not provided!'.format(self.__class__.__name__))
+
+ def __str__(self):
+ '''
+ String method, mainly used for debugging purposes
+ '''
+ return "Base Publication"
+
diff --git a/trunk/server/src/uds/core/services/BaseService.py b/trunk/server/src/uds/core/services/BaseService.py
new file mode 100644
index 00000000..ebbc59ee
--- /dev/null
+++ b/trunk/server/src/uds/core/services/BaseService.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from django.utils.translation import ugettext_noop as translatable
+from uds.core import Module
+
+class Service(Module):
+ '''
+ This class is in fact an interface, and represents a service, that is the
+ definition of an offering for consumers (users).
+
+ Class derived from this one declares the behavior of the service, as well
+ as custom parameter that will be needed to provide final consumable elements
+ to users.
+
+ The behavior attributes must be declared always, although they have default
+ values, this can change in a future and declaring all needed is a good way
+ to avoid future problems. Of course, if you declare that do no do something
+ (i.e. do not uses cache), you will not have to declare related attributes
+ (i.e. cacheTooltip, usesCache_L2 and cacheTooltip_L2)
+
+ As you derive from this class, if you provide __init__ in your own class,
+ remember to call ALWAYS at base class __init__ as this:
+
+ super(self.__class__, self).__init__(dbAuth, environment, values)
+
+ This is a MUST (if you override __init__), so internal structured gets
+ filled correctly, so don't forget it!.
+
+ The preferred method of provide initialization is to provide the :py:meth:`.initialize`,
+ and do not override __init__ method. This (initialize) will be invoked after
+ all internal initialization, so there will be available parent, environment and storage.
+
+ Normally objects of classes deriving from this one, will be serialized, called,
+ deserialized. This means that all that you want to ensure that is kept inside
+ the class must be serialized and unserialized, because there is no warrantee that
+ the object will get two methods invoked without haven't been removed from memory
+ and loaded again. One thing to have into account on this are Form Fields, that
+ default implementation marshals and unmashals them, so if your case is that you
+ only need data that is keeped at form fields, marshal and unmarshal and in fact
+ not needed.
+
+ '''
+
+ #: Constant for indicating that max elements this service can deploy is unlimited.
+ UNLIMITED = -1
+
+ #: Name of type, used at administration interface to identify this
+ #: service (i.e. Xen server, oVirt Server, ...)
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeName = translatable('Base Service')
+
+ #: Name of type used by Managers to identify this type of service
+ #: We could have used here the Class name, but we decided that the
+ #: module implementator will be the one that will provide a name that
+ #: will relation the class (type) and that name.
+ typeType = 'BaseService'
+
+ #: Description shown at administration level for this service.
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeDescription = translatable('Base Service')
+
+ #: Icon file, used to represent this service at administration interface
+ #: This file should be at same folder as this class is, except if you provide
+ #: your own :py:meth:uds.core.BaseModule.BaseModule.icon method.
+ iconFile = 'service.png'
+
+ # Functional related data
+
+ #: Normally set to UNLIMITED. This attribute indicates if the service has some "limitation"
+ #: for providing deployed services to users. This attribute can be set here or
+ #: modified at instance level, core will access always to it using an instance object.
+ maxDeployed = UNLIMITED #: If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
+
+ #: If this class uses cache or not. If uses cache is true, means that the
+ #: service can "prepare" some user deployments to allow quicker user access
+ #: to services if he already do not have one.
+ #: If you set this to True, please, provide a translatable :py:attr:.cacheToolTip
+ usesCache = False
+
+ #: Tooltip to be used if services uses cache at administration interface, indicated by :py:attr:.usesCache
+ cacheTooltip = translatable('None') #: Tooltip shown to user when this item is pointed at admin interface
+
+ #: If user deployments can be cached (see :py:attr:.usesCache), may he also can provide a secondary cache,
+ #: that is no more that user deployments that are "almost ready" to be used, but preperably consumes less
+ #: resources than L1 cache. This can give a boost to cache L1 recovering in case of peaks
+ #: in demand. If you set this to True, please, provide also a translatable :py:attr:.cacheTooltip_L2
+ usesCache_L2 = False #: If we need to generate a "Level 2" cache for this service (i.e., L1 could be running machines and L2 suspended machines)
+
+ #: Tooltip to be used if services uses L2 cache at administration interface, indicated by :py:attr:.usesCache_L2
+ cacheTooltip_L2 = translatable('None') #: Tooltip shown to user when this item is pointed at admin interface
+
+ #: If the service needs a o.s. manager (see os managers section)
+ needsManager = False
+
+ #: If the service can be autoassigned or needs to be assigned by administrator
+ #: Not all services are for assigning it. Thing, i.e., a Service that manages
+ #: a number of Server. The desired behavior will be to let administrator
+ #: the service to a user in the administration interface, an not the system
+ #: to assign the service automatically. If this is true, the core will not
+ #: assign the service automatically, so if the user do not have a consumable
+ #: assigned, the user will never get one (of this kind, of course)
+ mustAssignManually = False
+
+ #: Types of publications (preparated data for deploys)
+ #: If you provide this, UDS will assume that the service needs a preparation.
+ #: If not provided (it is None), UDS will assume that service do not needs
+ #: preparation. Take care, if you mark a service as it uses cache, you MUST
+ #: provide a publication type
+ #: This refers to class that provides the logic for publication, you can see
+ #: :py:class:uds.core.services.Publication
+ publicationType = None
+
+ #: Types of deploys (services in cache and/or assigned to users)
+ #: This is ALWAYS a MUST. You mast indicate the class responsible
+ #: for managing the user deployments (user consumable services generated
+ #: from this one). If this attribute is not set, the service will never work
+ #: (core will not know how to handle the user deployments)
+ deployedType = None
+
+ def __init__(self, environment, parent, values = None):
+ '''
+ Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, parent, values)".
+ We want to use the env, parent methods outside class. If not called, you must implement your own methods
+ cache and storage are "convenient" methods to access _env.cache() and _env.storage()
+ '''
+ super(Service, self).__init__(environment, values)
+ self._provider = parent
+ self.initialize(values)
+
+ def initialize(self, values):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base methods.
+ This will get invoked when all initialization stuff is done
+
+ Args:
+ Values: If values is not none, this object is being initialized
+ from administration interface, and not unmarshal will be done.
+ If it's None, this is initialized internally, and unmarshal will
+ be called after this.
+
+ Default implementation does nothing
+ '''
+ pass
+
+ def parent(self):
+ '''
+ Utility method to access parent provider for this service
+
+ Returns
+
+ Parent provider instance object (not database object)
+ '''
+ return self._provider
+
+ def requestServicesForAssignation(self, **kwargs):
+ '''
+ override this if mustAssignManualy is True
+ @params kwargs: Named arguments
+ @return an array with the services that we can assign (they must be of type deployedType)
+ We will access the returned array in "name" basis. This means that the service will be assigned by "name", so be care that every single service
+ returned are not repeated... :-)
+ '''
+ raise Exception('The class {0} has been marked as manually asignable but no requestServicesForAssignetion provided!!!'.format(self.__class__.__name__))
+
+ def __str__(self):
+ '''
+ String method, mainly used for debugging purposes
+ '''
+ return "Base Service Provider"
+
diff --git a/trunk/server/src/uds/core/services/BaseServiceProvider.py b/trunk/server/src/uds/core/services/BaseServiceProvider.py
new file mode 100644
index 00000000..7dbd6d78
--- /dev/null
+++ b/trunk/server/src/uds/core/services/BaseServiceProvider.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from uds.core import Module
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class ServiceProvider(Module):
+ '''
+ Base Service Provider Class.
+
+ All classes that will represent a service provider will need to be derived
+ from this class.
+
+ The preferred way of using this class is by its alias name, provided
+ at uds.core.services module, ServiceProvider.
+
+ This is a very basic class, intended to be the root class of services.
+ This means that services are childs of this class, declared at "offers" attribute.
+
+ As you derive from this class, if you provide __init__ in your own class,
+ remember to call ALWAYS at base class __init__ as this:
+
+ super(...., self).__init__(environment, values)
+
+ The preferred method of provide initialization is to provide the :py:meth:`.initialize`,
+ and do not overrie __init__ method. This (initialize) will be invoked after
+ all internal initialization.
+
+ This is a MUST, so internal structured gets filled correctly, so don't forget it!.
+
+ Normally objects of classes deriving from this one, will be serialized, called,
+ deserialized. This means that all that you want to ensure that is keeped inside
+ the class must be serialized and unserialized, because there is no warantee that
+ the object will get two methods invoked without haven't been removed from memory
+ and loaded again. One thing to have into account on this are Form Fields, that
+ default implementation marshals and unmashals them, so if your case is that you
+ only need data that is keeped at form fields, marshal and unmarshal and in fact
+ not needed.
+ '''
+
+ #: Services that we offers. Here is a list of service types (python types) that
+ #: this class will provide. This types are the python clases, derived from
+ #: Service, that are childs of this provider
+ offers = []
+
+ #: Name of type, used at administration interface to identify this
+ #: provider (i.e. Xen server, oVirt Server, ...)
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeName = 'Base Provider'
+
+ #: Name of type used by Managers to identify this tipe of service
+ #: We could have used here the Class name, but we decided that the
+ #: module implementator will be the one that will provide a name that
+ #: will relation the class (type) and that name.
+ typeType = 'BaseServiceProvider'
+
+ #: Description shown at administration level for this provider.
+ #: This string will be translated when provided to admin interface
+ #: using ugettext, so you can mark it as "translatable" at derived classes (using ugettext_noop)
+ #: if you want so it can be translated.
+ typeDescription = 'Base Service Provider'
+
+ #: Icon file, used to represent this provider at administration interface
+ #: This file should be at same folder as this class is, except if you provide
+ #: your own py:meth:`uds.core.BaseModule.BaseModule.icon` method.
+ iconFile = 'provider.png'
+
+ @classmethod
+ def getServicesTypes(cls):
+ '''
+ Returns what type of services this provider offers
+ '''
+ return cls.offers
+
+ @classmethod
+ def getServiceByType(cls, typeName):
+ '''
+ Tries to locate a child service which type corresponds with the
+ one provided.
+ Returns None if can't find one.
+
+ :note: The type that this method looks for is not the class, but
+ the typeType that Service has.
+ '''
+ res = None
+ for _type in cls.offers:
+ if _type.type() == typeName:
+ res = _type
+ break
+ return res
+
+
+ def __init__(self, environment, values = None):
+ '''
+ Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, values)"
+ if you override this method. Better is to provide an "__initialize__" method, that will be invoked
+ by __init__
+ Values parameter is provided (are not None) when creating or modifying the service provider, so params check should ocur here and, if not
+ valid, raise an "ValidationException" message
+ '''
+ super(ServiceProvider, self).__init__(environment, values)
+ self.initialize(values)
+
+
+ def initialize(self, values):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base methods.
+ This will get invoked when all initialization stuff is done
+
+ Args:
+ Values: If values is not none, this object is being initialized
+ from administration interface, and not unmarshal will be done.
+ If it's None, this is initialized internally, and unmarshal will
+ be called after this.
+
+ Default implementation does nothing
+ '''
+ pass
+
+ def __str__(self):
+ '''
+ Basic implementation, mostly used for debuging and testing, never used
+ at user or admin interfaces.
+ '''
+ return "Base Service Provider"
+
diff --git a/trunk/server/src/uds/core/services/Exceptions.py b/trunk/server/src/uds/core/services/Exceptions.py
new file mode 100644
index 00000000..f7c03975
--- /dev/null
+++ b/trunk/server/src/uds/core/services/Exceptions.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+class UnsupportedException(Exception):
+ '''
+ Reflects that we request an operation that is not supported, i.e. Cancel a publication with snapshots
+ '''
+ pass
+
+class OperationException(Exception):
+ '''
+ Reflects that the operation requested can't be acomplished, i.e. remove an snapshot without snapshot reference, cancel non running operation, etc...
+ '''
+ pass
+
+class PublishException(Exception):
+ '''
+ Reflects thate the publication can't be done for causes we don't know in advance
+ '''
+ pass
+
+class DeploymentException(Exception):
+ '''
+ Reflects that a deployment of a service (at cache, or assigned to user) can't be done for causes we don't know in advance
+ '''
+ pass
+
+class CancelException(Exception):
+ '''
+ Reflects that a "cancel" operation can't be done for some reason
+ '''
+
+class InvalidServiceException(Exception):
+ '''
+ Invalid service specified. The service is not ready
+ '''
+ pass
+
+class MaxServicesReachedException(Exception):
+ '''
+ Number of maximum services has been reached, and no more services
+ can be created for users.
+ '''
+ pass
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/services/ServiceProviderFactory.py b/trunk/server/src/uds/core/services/ServiceProviderFactory.py
new file mode 100644
index 00000000..5936ca2c
--- /dev/null
+++ b/trunk/server/src/uds/core/services/ServiceProviderFactory.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+import logging
+
+logger = logging.getLogger(__name__)
+
+class ServiceProviderFactory(object):
+ '''
+ This class holds the register of all known service provider modules
+ inside UDS.
+
+ It provides a way to register and recover providers providers.
+ '''
+ _factory = None
+
+ def __init__(self):
+ '''
+ Initializes internal dictionary for service providers registration
+ '''
+ self._providers = {}
+
+ @staticmethod
+ def factory():
+ '''
+ Returns the factory that keeps the register of service providers.
+ '''
+ if ServiceProviderFactory._factory == None:
+ ServiceProviderFactory._factory = ServiceProviderFactory()
+ return ServiceProviderFactory._factory
+
+ def providers(self):
+ '''
+ Returns the list of service providers already registered.
+ '''
+ return self._providers
+
+ def insert(self, type_):
+ '''
+ Inserts type_ as a service provider
+ '''
+ # Before inserting type, we will make a couple of sanity checks
+ # We could also check if it provides at least a service, but
+ # for debugging purposes, it's better to not check that
+ # We will check that if service provided by "provider" needs
+ # cache, but service do not provides publicationType,
+ # that service will not be registered and it will be informed
+ offers = []
+ for s in type_.offers:
+ if s.usesCache_L2 is True:
+ s.usesCache = True
+ if s.usesCache is True and s.publicationType is None:
+ logger.error('Provider {0} offers {1}, but {1} needs cache and do not have publicationType defined'.format(
+ type_, s))
+ continue
+ offers.append(s)
+
+ # Only offers valid services
+ type_.offers = offers
+ logger.debug('Adding provider {0} as {1}'.format(type_.type(), type_))
+ self._providers[type_.type()] = type_
+
+ def lookup(self, typeName):
+ '''
+ Tries to locate a server provider and by its name, and, if
+ not found, returns None
+ '''
+ return self._providers.get(typeName, None)
+
+ def servicesThatDoNotNeedPublication(self):
+ '''
+ Returns a list of all service providers registered that do not need
+ to be published
+ '''
+ res = []
+ for p in self._providers.values():
+ for s in p.offers:
+ if s.publicationType is None and s.mustAssignManually is False:
+ res.append(s)
+ return res
diff --git a/trunk/server/src/uds/core/services/__init__.py b/trunk/server/src/uds/core/services/__init__.py
new file mode 100644
index 00000000..55d2ea5f
--- /dev/null
+++ b/trunk/server/src/uds/core/services/__init__.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS Service modules interfaces and classes.
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from BaseServiceProvider import ServiceProvider
+from BaseService import Service
+from BasePublication import Publication
+from BaseDeployed import UserDeployment
+import Exceptions
+
+def factory():
+ '''
+ Returns factory for register/access to service providers
+ '''
+ from ServiceProviderFactory import ServiceProviderFactory
+ return ServiceProviderFactory.factory()
diff --git a/trunk/server/src/uds/core/services/provider.png b/trunk/server/src/uds/core/services/provider.png
new file mode 100644
index 00000000..d2a954d4
Binary files /dev/null and b/trunk/server/src/uds/core/services/provider.png differ
diff --git a/trunk/server/src/uds/core/services/service.png b/trunk/server/src/uds/core/services/service.png
new file mode 100644
index 00000000..c7b626c4
Binary files /dev/null and b/trunk/server/src/uds/core/services/service.png differ
diff --git a/trunk/server/src/uds/core/transports/BaseTransport.py b/trunk/server/src/uds/core/transports/BaseTransport.py
new file mode 100644
index 00000000..6d9c95e4
--- /dev/null
+++ b/trunk/server/src/uds/core/transports/BaseTransport.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.util import OsDetector
+from uds.core import Module
+import protocols
+
+class Transport(Module):
+ '''
+ An OS Manager is responsible for communication the service the different actions to take (i.e. adding a windows machine to a domain)
+ The Service (i.e. virtual machine) communicates with the OSManager via a published web method, that must include the unique ID.
+ In order to make easier to agents identify themselfs, the Unique ID can be a list with various Ids (i.e. the macs of the virtual machine).
+ Server will iterate thought them and look for an identifier associated with the service. This list is a comma separated values (i.e. AA:BB:CC:DD:EE:FF,00:11:22:...)
+ Remember also that we inherit the test and check methods from BaseModule
+ '''
+ # Transport informational related data, inherited from BaseModule
+ typeName = 'Base Transport Manager'
+ typeType = 'Base Transport'
+ typeDescription = 'Base Transport'
+ iconFile = 'transport.png'
+ needsJava = False # If this transport needs java for rendering
+ # Supported names for OS (used right now, but lots of more names for sure)
+ # Windows
+ # Macintosh
+ # Linux
+ supportedOss = [OsDetector.Linux, OsDetector.Windows, OsDetector.Macintosh] # Supported operating systems
+
+ # If this transport is visible via Web, via Thick Client or both
+ webTransport = False
+ tcTransport = False
+
+ def __init__(self,environment, values):
+ super(Transport, self).__init__(environment, values)
+ self.initialize(values)
+
+ def initialize(self, values):
+ '''
+ This method will be invoked from __init__ constructor.
+ This is provided so you don't have to provide your own __init__ method,
+ and invoke base methods.
+ This will get invoked when all initialization stuff is done
+
+ Args:
+ Values: If values is not none, this object is being initialized
+ from administration interface, and not unmarshal will be done.
+ If it's None, this is initialized internally, and unmarshal will
+ be called after this.
+
+ Default implementation does nothing
+ '''
+ pass
+
+ def destroy(self):
+ '''
+ Invoked when Transport is deleted
+ '''
+ pass
+
+ def isAvailableFor(self, ip):
+ '''
+ Checks if the transport is available for the requested destination ip
+ Override this in yours transports
+ '''
+ return False
+
+ @classmethod
+ def supportsOs(cls, osName):
+ '''
+ Helper method to check if transport supports requested operating system.
+ Class method
+ '''
+ return cls.supportedOss.count(osName) > 0
+
+ @classmethod
+ def providesConnetionInfo(cls):
+ '''
+ Helper method to check if transport provides information about connection
+ '''
+ return cls.getConnectionInfo != Transport.getConnectionInfo
+
+ def getConnectionInfo(self, service, user, password):
+ '''
+ This method must provide information about connection.
+ We don't have to implement it, but if we wont to allow some types of connections
+ (such as Client applications, some kinds of TC, etc... we must provide it or those
+ kind of terminals/application will not work
+
+ Args:
+ userService: DeployedUserService for witch we are rendering the connection (db model), or DeployedService (db model)
+ user: user (dbUser) logged in
+ pass: password used in authentication
+
+ The expected result from this method is a dictionary, containing at least:
+ 'protocol': protocol to use, (there are a few standard defined in 'protocols.py', if yours does not fit those, use your own name
+ 'username': username (transformed if needed to) used to login to service
+ 'password': password (transformed if needed to) used to login to service
+ 'domain': domain (extracted from username or wherever) that will be used. (Not necesarily an AD domain)
+
+ :note: The provided service can be an user service or an deployed service (parent of user services).
+ I have implemented processUserPassword in both so in most cases we do not need if the service is
+ DeployedService or UserService. In case of processUserPassword for an DeployedService, no transformation
+ is done, because there is no relation at that level between user and service.
+ '''
+ return {'protocol': protocols.NONE, 'username': '', 'password': '', 'domain': ''}
+
+ def renderForHtml(self, userService, idUserService, idTransport, ip, os, user, password):
+ '''
+ Requests the html rendering of connector for the destination ip, (dbUser) and password
+ @param: userService: DeployedUserService for witch we are rendering the connection (db model)
+ @param idUserService: id of the user service ((scrambled). You will need this to "notify" anythig to broker (such as log, hostname of client, ip, ...)
+ @param idTransport: id of the transport (scrambled)
+ @param ip: ip of the destination
+ @param user: user (dbUser) logged in
+ @param pass: password used in authentication
+ '''
+ return _('Transport empty')
+
+ def getHtmlComponent(self, id, os, componentId):
+ '''
+ This is a method to let the transport add own components (images, applets, or whatever) to the rendered html
+ The reference to object will be the access to the uds.web.views.transcomp, with parameters transportId = ourTransportId and
+ componentId = one id recognized by this method
+ We expect an return array, with first parameter as mime/type and second the content to return
+ '''
+ return ['text/plain', '']
+
+ def __str__(self):
+ return "Base OS Manager"
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/transports/TransportsFactory.py b/trunk/server/src/uds/core/transports/TransportsFactory.py
new file mode 100644
index 00000000..27532c8b
--- /dev/null
+++ b/trunk/server/src/uds/core/transports/TransportsFactory.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+import logging
+
+logger = logging.getLogger(__name__)
+
+class TransportsFactory(object):
+ _factory = None
+
+ def __init__(self):
+ self._jobs = {}
+
+ @staticmethod
+ def factory():
+ if TransportsFactory._factory == None:
+ TransportsFactory._factory = TransportsFactory()
+ return TransportsFactory._factory
+
+ def providers(self):
+ return self._jobs
+
+ def insert(self, type):
+ logger.debug('Adding transport {0} as {1}'.format(type.type(), type))
+ self._jobs[type.type()] = type
+
+ def lookup(self, typeName):
+ try:
+ return self._jobs[typeName]
+ except KeyError:
+ return None
diff --git a/trunk/server/src/uds/core/transports/__init__.py b/trunk/server/src/uds/core/transports/__init__.py
new file mode 100644
index 00000000..32cd879e
--- /dev/null
+++ b/trunk/server/src/uds/core/transports/__init__.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+UDS Service modules interfaces and classes.
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+from BaseTransport import Transport
+
+def factory():
+ '''
+ Returns factory for register/access to service providers
+ '''
+ from TransportsFactory import TransportsFactory
+ return TransportsFactory.factory()
diff --git a/trunk/server/src/uds/core/transports/protocols.py b/trunk/server/src/uds/core/transports/protocols.py
new file mode 100644
index 00000000..ea0b5314
--- /dev/null
+++ b/trunk/server/src/uds/core/transports/protocols.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+NONE = ''
+RDP = 'rdp'
+RGS = 'rgs'
+SPICE = 'spice'
+VNC = 'vnc'
+PCOIP = 'pcoip'
+REMOTEFX = 'remotefx'
+HDX = 'hdx'
+ICA = 'ica'
+NX = 'nx'
+X11 = 'x11'
+OTHER = 'other'
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/transports/transport.png b/trunk/server/src/uds/core/transports/transport.png
new file mode 100644
index 00000000..e572c72f
Binary files /dev/null and b/trunk/server/src/uds/core/transports/transport.png differ
diff --git a/trunk/server/src/uds/core/ui/UserInterface.py b/trunk/server/src/uds/core/ui/UserInterface.py
new file mode 100644
index 00000000..f1501cb1
--- /dev/null
+++ b/trunk/server/src/uds/core/ui/UserInterface.py
@@ -0,0 +1,802 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+import cPickle
+import logging
+
+logger = logging.getLogger(__name__)
+
+class gui(object):
+ '''
+ This class contains the representations of fields needed by UDS modules and
+ administation interface.
+
+ This contains fields types, that modules uses to make a form and interact
+ with users.
+
+ The use of this provided fields are as follows:
+
+ The Module is descendant of "BaseModule", which also is inherited from this
+ class.
+
+ At class level, we declare the fields needed to interact with the user, as
+ this example:
+
+ .. code-block:: python
+
+ class AuthModule(Authenticator):
+ # ...
+ # Other initializations
+ # ...
+ users = gui.EditableList(label = 'Users', tooltip = 'Select users',
+ order = 1, values = ['user1', 'user2', 'user3', 'user4'])
+ passw = gui.Password(label='Pass', length=32, tooltip='Password',
+ order = 2, required = True, defValue = '12345')
+ # ...
+ # more fields
+ # ...
+
+ At class instantiation, this data is extracted and processed, so the admin
+ can access this form to let users
+ create new instances of this module.
+ '''
+
+ #: True string value
+ TRUE = 'true'
+ #: False string value
+ FALSE = 'false'
+
+ #: Static Callbacks simple registry
+ callbacks = {}
+
+ # Helpers
+ @staticmethod
+ def convertToChoices(vals):
+ '''
+ Helper to convert from array of strings to the same dict used in choice,
+ multichoice, ..
+ The id is set to values in the array (strings), while text is left empty.
+ '''
+ res = []
+ for v in vals:
+ res.append( { 'id' : v, 'text' : '' } )
+ return res
+
+ @staticmethod
+ def choiceItem(id_, text):
+ '''
+ Helper method to create a single choice item.
+
+ Args:
+ id: Id of the choice to create
+
+ text: Text to assign to the choice to create
+
+ Returns:
+ An dictionary, that is the representation of a single choice item,
+ with 2 keys, 'id' and 'text'
+
+ :note: Text can be anything, the method converts it first to text before
+ assigning to dictionary
+ '''
+ return { 'id' : str(id_), 'text' : str(text) }
+
+ @staticmethod
+ def strToBool(str_):
+ '''
+ Converts the string "true" (case insensitive) to True (boolean).
+ Anything else is converted to false
+
+ Args:
+ str: Str to convert to boolean
+
+ Returns:
+ True if the string is "true" (case insensitive), False else.
+ '''
+ if str_.lower() == gui.TRUE:
+ return True
+ return False
+
+ @staticmethod
+ def boolToStr(bol):
+ '''
+ Converts a boolean to the string representation. True is converted to
+ "true", False to "false".
+
+ Args:
+ bol: Boolean value (True or false) to convert
+
+ Returns:
+ "true" if bol evals to True, "false" if don't.
+ '''
+ if bol:
+ return gui.TRUE
+ return gui.FALSE
+
+ # Classes
+
+ class InputField(object):
+ '''
+ Class representing an simple input field.
+ This class is not directly usable, must be used by any inherited class
+ (fields all of them)
+ All fields are inherited from this one
+
+ The data managed for an input field, and their default values are:
+ * length: Max length of the field. Defaults to DEFAULT_LENGTH
+ * required: If this field is a MUST. defaults to false
+ * label: Label used with this field. Defaults to ''
+ * defvalue: Default value for the field. Defaults to '' (this is
+ always an string)
+ * rdonly: If the field is read only on modification. On creation,
+ all fields are "writable". Defaults to False
+ * order: order inside the form, defaults to 0 (if two or more fields
+ has same order, the output order may be anything)
+ * tooltip: Tooltip used in the form, defaults to ''
+ * type: type of the input field, defaults to "text box" (TextField)
+
+ In every single field, you must at least indicate:
+ * if required or not
+ * order
+ * label
+ * tooltip
+ * defvalue
+ * rdonly if can't be modified once it's created
+
+ Any other paremeter needed is indicated in the corresponding field class.
+
+ Also a value field is available, so you can get/set the form field value.
+ This property expects always an string, no matter what kind of field it is.
+
+ Take into account also that "value" has precedence over "defValue",
+ so if you use both, the used one will be "value". This is valid for
+ all form fields.
+ '''
+ TEXT_TYPE = 'text'
+ TEXTBOX_TYPE = 'textbox'
+ NUMERIC_TYPE = 'numeric'
+ PASSWORD_TYPE = 'password'
+ HIDDEN_TYPE = 'hidden'
+ CHOICE_TYPE = 'choice'
+ MULTI_CHOICE_TYPE = 'multichoice'
+ EDITABLE_LIST = 'editlist'
+ CHECKBOX_TYPE = 'checkbox'
+
+ DEFAULT_LENTGH = 32 #: If length of some fields are not especified, this value is used as default
+
+
+
+ def __init__(self, **options):
+ self._data = {
+ 'length' : options.get('length', gui.InputField.DEFAULT_LENTGH),
+ 'required' : options['required'] if options.has_key('required') else False,
+ 'label': options['label'] if options.has_key('label') else '',
+ 'defvalue' : str(options['defvalue']) if options.has_key('defvalue') else '',
+ 'rdonly' : options['rdonly'] if options.has_key('rdonly') else False, # This property only affects in "modify" operations
+ 'order' : options['order'] if options.has_key('order') else 0,
+ 'tooltip' : options['tooltip'] if options.has_key('tooltip') else '',
+ 'type' : gui.InputField.TEXT_TYPE,
+ 'value' : options['value'] if options.has_key('value') else '',
+ }
+
+ def _type(self, type_):
+ '''
+ Sets the type of this field.
+
+ Args:
+ type: Type to set (from constants of this class)
+ '''
+ self._data['type'] = type_
+
+ def isType(self, type_):
+ '''
+ Returns true if this field is of specified type
+ '''
+ return self._data['type'] == type_
+
+ @property
+ def value(self):
+ '''
+ Obtains the stored value
+ '''
+ return self._data['value']
+
+ @value.setter
+ def value(self, value):
+ '''
+ Stores new value (not the default one)
+ '''
+ self._setValue(value)
+
+ def _setValue(self, value):
+ '''
+ So we can override value setting at descendants
+ '''
+ self._data['value'] = value
+
+
+ def guiDescription(self):
+ '''
+ Returns the dictionary with the description of this item.
+ We copy it, cause we need to translate the label and tooltip fields
+ and don't want to
+ alter original values.
+ '''
+ data = self._data.copy()
+ data['label'] = _(data['label'])
+ data['tooltip'] = _(data['tooltip'])
+ return data
+
+ @property
+ def defValue(self):
+ '''
+ Returns the default value for this field
+ '''
+ return self._data['defvalue']
+
+ @defValue.setter
+ def defValue(self, defValue):
+ self.setDefValue(defValue)
+
+ def setDefValue(self, defValue):
+ '''
+ Sets the default value of the field·
+
+ Args:
+ defValue: Default value (string)
+ '''
+ self._data['defvalue'] = defValue
+
+
+ class TextField(InputField):
+ '''
+ This represents a text field.
+
+ The values of parameters are inherited from :py:class:`InputField`
+
+ Additionally to standard parameters, the length parameter is a
+ recommended one for this kind of field.
+
+ You can specify that this is a multiline text box with **multiline**
+ parameter. If it exists, and is greater than 1, indicates how much
+ lines will be used to display field. (Max number is 8)
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares an text form field, with label "Host", tooltip
+ # "Host name for this module", that is required,
+ # with max length of 64 chars and order = 1, and is editable
+ # after creation.
+ host = gui.TextField(length=64, label = _('Host'), order = 1,
+ tooltip = _('Host name for this module'), required = True)
+
+ # Declares an text form field, with label "Other",
+ # tooltip "Other info", that is not required, that is not
+ # required and that is not editable after creation.
+ other = gui.TextField(length=64, label = _('Other'), order = 1,
+ tooltip = _('Other info'), rdonly = True)
+
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._type(gui.InputField.TEXT_TYPE)
+ multiline = int(options.get('multiline', 0))
+ if multiline > 8:
+ multiline = 8
+ self._data['multiline'] = multiline
+
+
+ class NumericField(InputField):
+ '''
+ This represents a numeric field. It apears with an spin up/down button.
+
+ The values of parameres are inherited from :py:class:`InputField`
+
+ Additionally to standard parameters, the length parameter indicates the
+ max number of digits (0-9 values).
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares an numeric form field, with max value of 99999, label
+ # "Port", that is required,
+ # with tooltip "Port (usually 443)" and order 1
+ num = gui.NumericField(length=5, label = _('Port'),
+ defvalue = '443', order = 1, tooltip = _('Port (usually 443)'),
+ required = True)
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._type(gui.InputField.NUMERIC_TYPE)
+
+ def num(self):
+ '''
+ Return value as integer
+ '''
+ return int(self.value)
+
+ class PasswordField(InputField):
+ '''
+ This represents a password field. It appears with "*" at input, so the contents is not displayed
+
+ The values of parameres are inherited from :py:class:`InputField`
+
+ Additionally to standard parameters, the length parameter is a recommended one for this kind of field.
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares an text form field, with label "Password",
+ # tooltip "Password of the user", that is required,
+ # with max length of 32 chars and order = 2, and is
+ # editable after creation.
+ passw = gui.PasswordField(lenth=32, label = _('Password'),
+ order = 4, tooltip = _('Password of the user'),
+ required = True)
+
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._type(gui.InputField.PASSWORD_TYPE)
+
+ class HiddenField(InputField):
+ '''
+ This represents a hidden field. It is not displayed to the user. It use
+ is for keeping info at form needed
+ by module, but not editable by user (i.e., one service can keep info
+ about the parent provider in hiddens)
+
+ The values of parameres are inherited from :py:class:`InputField`
+
+ These are almost the same as TextFields, but they do not get displayed
+ for user interaction.
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares an empty hidden field
+ hidden = gui.HiddenField()
+
+
+ After that, at initGui method of module, we can store a value inside
+ using setDefValue as shown here:
+
+ .. code-block:: python
+
+ def initGui(self):
+ # always set defValue using self, cause we only want to store
+ # value for current instance
+ self.hidden.setDefValue(self.parent().serialize())
+
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._type(gui.InputField.HIDDEN_TYPE)
+
+ class CheckBoxField(InputField):
+ '''
+ This represents a check box field, with values "true" and "false"
+
+ The values of parameters are inherited from :py:class:`InputField`
+
+ The valid values for this defvalue are: "true" and "false" (as strings)
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares an check box field, with label "Use SSL", order 3,
+ # tooltip "If checked, will use a ssl connection", default value
+ # unchecked (not included, so it's empty, so it's not true :-))
+ ssl = gui.CheckBoxField(label = _('Use SSL'), order = 3,
+ tooltip = _('If checked, will use a ssl connection'))
+
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._type(gui.InputField.CHECKBOX_TYPE)
+
+ def isTrue(self):
+ '''
+ Checks that the value is true
+ '''
+ return self.value == 'true'
+
+ class ChoiceField(InputField):
+ '''
+ This represents a simple combo box with single selection.
+
+ The values of parameters are inherited from :py:class:`InputField`
+
+ ChoiceField needs a function to provide values inside it.
+
+ * We specify the values via "values" option this way:
+
+ Example:
+
+ .. code-block:: python
+
+ choices = gui.ChoiceField(label="choices", values = [ {'id':'1',
+ 'text':'Text 1'}, {'id':'xxx', 'text':'Text 2'}])
+
+ You can specify a multi valuated field via id-values, or a
+ single-valued field via id-value
+
+ * We can override choice values at UserInterface derived class
+ constructor or initGui using setValues
+
+ There is an extra option available for this kind of field:
+
+ fills: This options is a dictionary that contains this fields:
+ * 'callbackName' : Callback name for invocation via the specific
+ method xml-rpc. This name is a name we assign to this callback,
+ and is used to locate the method when callback is invoked from
+ admin interface.
+ * 'function' : Function to execute.
+
+ This funtion receives one parameter, that is a dictionary with
+ all parameters (that, in time, are fields names) that we have
+ requested.
+
+ The expected return value for this callback is an array of
+ dictionaries with fields and values to set, as
+ example show below shows.
+ * 'parameters' : Array of field names to pass back to server so
+ it can obtain the results.
+
+ Of course, this fields must be part of the module.
+
+ Example:
+
+ .. code-block:: python
+
+ choice1 = gui.ChoiceField(label="Choice 1", values = ....,
+ fills = { 'target': 'choice2', 'callback': fncValues,
+ 'parameters': ['choice1', 'name']}
+ )
+ choice2 = ghui.ChoiceField(label="Choice 2")
+
+ Here is a more detailed explanation, using the VC service module as
+ sample.
+
+ .. code-block:: python
+
+ class VCHelpers(object):
+ # ...
+ # other stuff
+ # ...
+ @staticmethod
+ def getMachines(parameters):
+ # ...initialization and other stuff...
+ if parameters['resourcePool'] != '':
+ # ... do stuff ...
+ data = [ { 'name' : 'machine', 'values' : 'xxxxxx' } ]
+ return data
+
+ class ModuleVC(services.Service)
+ # ...
+ # stuff
+ # ...
+ resourcePool = gui.ChoiceField(
+ label=_("Resource Pool"), rdonly = False, order = 5,
+ fills = {
+ 'callbackName' : 'vcFillMachinesFromResource',
+ 'function' : VCHelpers.getMachines,
+ 'parameters' : ['vc', 'ev', 'resourcePool']
+ },
+ tooltip = _('Resource Pool containing base machine'),
+ required = True
+ )
+
+ machine = gui.ChoiceField(label = _("Base Machine"), order = 6,
+ tooltip = _('Base machine for this service'), required = True )
+
+ vc = gui.HiddenField()
+ ev = gui.HiddenField() # ....
+
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._data['values'] = options['values'] if options.has_key('values') else []
+ if options.has_key('fills'):
+ # Save fnc to register as callback
+ fills = options['fills']
+ fnc = fills['function']
+ fills.pop('function')
+ self._data['fills'] = fills
+ gui.callbacks[fills['callbackName']] = fnc
+ self._type(gui.InputField.CHOICE_TYPE)
+
+ def setValues(self, values):
+ '''
+ Set the values for this choice field
+ '''
+ self._data['values'] = values
+
+ class MultiChoiceField(InputField):
+ '''
+ Multichoices are list of items that are multi-selectable.
+
+ There is a new parameter here, not covered by InputField:
+ * 'rows' to tell gui how many rows to display (the length of the
+ displayable list)
+
+ "defvalue" is expresed as a comma separated list of ids
+
+ This class do not have callback support, as ChoiceField does.
+
+ The values is an array of dictionaries, in the form [ { 'id' : 'a',
+ 'text': b }, ... ]
+
+ Example usage:
+
+ .. code-block:: python
+
+ # Declares a multiple choices field, with label "Datastores", that
+ is editable, with 5 rows for displaying
+ # data at most in user interface, 8th in order, that is required
+ and has tooltip "Datastores where to put incrementals",
+ # this field is required and has 2 selectable items: "datastore0"
+ with id "0" and "datastore1" with id "1"
+ datastores = gui.MultiChoiceField(label = _("Datastores"),
+ rdonly = False, rows = 5, order = 8,
+ tooltip = _('Datastores where to put incrementals'),
+ required = True,
+ values = [ {'id': '0', 'text': 'datastore0' },
+ {'id': '1', 'text': 'datastore1' } ]
+ )
+ '''
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._data['values'] = options['values'] if options.has_key('values') else []
+ self._data['rows'] = options['rows'] if options.has_key('rows') else -1
+ self._type(gui.InputField.MULTI_CHOICE_TYPE)
+
+ def setValues(self, values):
+ '''
+ Set the values for this multi choice field
+ '''
+ self._data['values'] = values
+
+ class EditableList(InputField):
+ '''
+ Editables list are lists of editable elements (i.e., a list of IPs, macs,
+ names, etcc) treated as simple strings with no id
+
+ The struct used to pass values is an array of strings, i.e. ['1', '2',
+ 'test', 'bebito', ...]
+
+ This list don't have "selected" items, so its defvalue field is simply
+ ignored.
+
+ We only nee to pass in "label" and, maybe, "values" to set default
+ content for the list.
+
+ Keep in mind that this is an user editable list, so the user can insert
+ values and/or import values from files, so
+ by default it will probably have no content at all.
+
+ Example usage:
+
+ .. code-block:: python
+
+ #
+ ipList = gui.EditableList(label=_('List of IPS'))
+
+ '''
+
+ #: Constant for separating values at "value" method
+ SEPARATOR = '\001'
+
+ def __init__(self, **options):
+ super(self.__class__, self).__init__(**options)
+ self._data['values'] = gui.convertToChoices(options['values']) if options.has_key('values') else []
+ self._type(gui.InputField.EDITABLE_LIST)
+
+ def _setValue(self, values):
+ '''
+ So we can override value setting at descendants
+ '''
+ super(self.__class__, self)._setValue(values)
+ self._data['values'] = gui.convertToChoices(values)
+
+
+
+class UserInterfaceType(type):
+ '''
+ Metaclass definition for moving the user interface descriptions to a usable
+ better place
+ '''
+ def __new__(cls, classname, bases, classDict):
+ newClassDict = { }
+ _gui = {}
+ # We will keep a reference to gui elements also at _gui so we can access them easily
+ for attrName, attr in classDict.items():
+ if isinstance(attr, gui.InputField):
+ _gui[attrName] = attr
+ newClassDict[attrName] = attr
+ newClassDict['_gui'] = _gui
+ return type.__new__(cls, classname, bases, newClassDict)
+
+class UserInterface(object):
+ '''
+ This class provides the management for gui descriptions (user forms)
+
+ Once a class is derived from this one, that class can contain Field
+ Descriptions,
+ that will be managed correctly.
+
+ By default, the values passed to this class constructor are used to fill
+ the gui form fields values.
+ '''
+ __metaclass__ = UserInterfaceType
+
+ def __init__(self, values = None):
+ #: If there is an array of elements to initialize, simply try to store values on form fields
+ if values is not None:
+ for k, v in self._gui.iteritems():
+ if values.has_key(k):
+ v.value = values[k]
+
+
+ def initGui(self):
+ '''
+ This method gives the oportunity to initialize gui fields before they
+ are send to administartion client.
+ We need this because at initialization time we probably don't have the
+ data for gui.
+
+ :note: This method is used as a "trick" to allow to modify default form
+ data for services. Services are child of Service Providers, and
+ will probably need data from Provider to fill initial form data.
+ The rest of modules will not use this, and this only will be used
+ when the user requests a new service or wants to modify existing
+ one.
+ :note: There is a drawback of this, and it is that there is that this
+ method will modify service default data. It will run fast (probably),
+ but may happen that two services of same type are requested at same
+ time, and returned data will be probable a nonsense. We will take care
+ of this posibility in a near version...
+ '''
+ pass
+
+ def valuesDict(self):
+ '''
+ Returns own data needed for user interaction as a dict of key-names ->
+ values. The values returned must be strings.
+
+ Example:
+ we have 2 text field, first named "host" and second named "port",
+ we can do something like this:
+
+ .. code-block:: python
+
+ return { 'host' : self.host, 'port' : self.port }
+
+ (Just the reverse of :py:meth:`.__init__`, __init__ receives this
+ dict, valuesDict must return the dict)
+
+ Names must coincide with fields declared.
+
+ Returns:
+ Dictionary, associated with declared fields.
+ Default implementation returns the values stored at the gui form
+ fields declared.
+
+ :note: By default, the provided method returns the correct values
+ extracted from form fields
+
+ '''
+ dic = {}
+ for k, v in self._gui.iteritems():
+ if v.isType(gui.InputField.EDITABLE_LIST):
+ dic[k] = gui.convertToChoices(v.value)
+ else:
+ dic[k] = v.value
+ return dic
+
+
+ def serializeForm(self):
+ '''
+ All values stored at form fields are serialized and returned as a single
+ string
+ Separating char is
+
+ The returned string is zipped and then converted to base 64
+
+ Note: Hidens are not serialized, they are ignored
+
+ '''
+ arr = []
+ for k, v in self._gui.iteritems():
+ if v.isType(gui.InputField.HIDDEN_TYPE):
+ continue
+ if v.isType(gui.InputField.EDITABLE_LIST):
+ val = '\001' + cPickle.dumps(v.value)
+ else:
+ val = v.value
+ arr.append(k + '\003' + val)
+ return '\002'.join(arr).encode('zip')
+
+ def unserializeForm(self, values):
+ '''
+ This method unserializes the values previously obtained using
+ :py:meth:`serializeForm`, and stores
+ the valid values form form fileds inside its corresponding field
+ '''
+ if values == '': # Has nothing
+ return
+
+ # Set all values to defaults ones
+ for k in self._gui.iterkeys():
+ if self._gui[k].isType(gui.InputField.HIDDEN_TYPE): # Do not fills the value of hidden fields, those will not be deserialized
+ continue
+
+ self._gui[k].value = self._gui[k].defValue
+
+ for txt in values.decode('zip').split('\002'):
+ k, v = txt.split('\003')
+ if self._gui.has_key(k):
+ try:
+ if v[0] == '\001':
+ val = cPickle.loads(v[1:])
+ else:
+ val = v
+ except:
+ val = ''
+ self._gui[k].value = val
+
+ @classmethod
+ def guiDescription(cls, obj = None):
+ '''
+ This simple method generates the gui description needed by the
+ administration client, so it can
+ represent it at user interface and manage it.
+
+ Args:
+ object: If not none, object that will get its "initGui" invoked
+ This will only happen (not to be None) in Services.
+ '''
+ if obj is not None:
+ obj.initGui() # We give the "oportunity" to fill necesary gui data before providing it to client
+
+ res = []
+ for key, val in cls._gui.iteritems():
+ res.append( { 'name' : key, 'gui' : val.guiDescription(), 'value' : '' }, )
+ return res
diff --git a/trunk/server/src/uds/core/ui/__init__.py b/trunk/server/src/uds/core/ui/__init__.py
new file mode 100644
index 00000000..fd753903
--- /dev/null
+++ b/trunk/server/src/uds/core/ui/__init__.py
@@ -0,0 +1,8 @@
+'''
+User interface part of UDS modules.
+
+This module contains the definition of UserInterface, needed to describe the interaction
+between an UDS module and the administration interface
+'''
+
+from UserInterface import gui
diff --git a/trunk/server/src/uds/core/util/AutoAttributes.py b/trunk/server/src/uds/core/util/AutoAttributes.py
new file mode 100644
index 00000000..357ca38e
--- /dev/null
+++ b/trunk/server/src/uds/core/util/AutoAttributes.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.Serializable import Serializable
+import cPickle
+import timeit
+
+class Attribute(object):
+
+ def __init__(self, theType, value = None):
+ self._type = theType
+ self.setValue(value)
+
+ def getType(self):
+ return self._type
+
+ def getValue(self):
+ return self._value
+
+ def getStrValue(self):
+ return str(self._value)
+
+ def setValue(self, value):
+ if value is None:
+ self._value = self._type()
+ else:
+ self._value = self._type(value)
+
+class AutoAttributes(Serializable):
+ '''
+ Easy creation of attributes to marshal & unmarshal at modules
+ usage as base class (First class so yours inherits this "marshal" and "unmarshal"
+ initialize at init with super(myclass,self).__init__(attr1=type, attr2=type, ...)
+ or with declare(attr1=type,attr2=type,..)
+ Access attrs as "self._attr1, self._attr2"
+ '''
+
+ #: This codec is not intended to override Serializable codec
+ #: Serializable codec is for encoding marshaled data,
+ #: while this codec is for encoding pickled data from autoattributes
+ ACODEC = 'zip'
+
+ def __init__(self, **kwargs):
+ self.declare(**kwargs)
+
+ def __getattribute__(self, name):
+ if name.startswith('_') and self.dict.has_key(name[1:]):
+ return self.dict[name[1:]].getValue()
+ return object.__getattribute__(self, name)
+
+ def __setattr__(self, name, value):
+ if name.startswith('_') and self.dict.has_key(name[1:]):
+ self.dict[name[1:]].setValue(value)
+ else:
+ object.__setattr__(self, name, value)
+
+ def declare(self, **kwargs):
+ d = {}
+ for key,typ in kwargs.iteritems():
+ d[key] = Attribute(typ)
+ self.dict = d
+
+ def marshal(self):
+ return '\2'.join( [ '%s\1%s' % (k, cPickle.dumps(v)) for k, v in self.dict.iteritems() ] ).encode(AutoAttributes.ACODEC)
+
+ def unmarshal(self, data):
+ if data == '': # Can be empty
+ return
+ # We keep original data (maybe incomplete)
+ for pair in data.decode(AutoAttributes.ACODEC).split('\2'):
+ k, v = pair.split('\1')
+ self.dict[k] = cPickle.loads(v)
+
+ def __str__(self):
+ str = ''
diff --git a/trunk/server/src/uds/core/util/Cache.py b/trunk/server/src/uds/core/util/Cache.py
new file mode 100644
index 00000000..3773afb8
--- /dev/null
+++ b/trunk/server/src/uds/core/util/Cache.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import transaction
+from uds.models import Cache as dbCache, getSqlDatetime
+from datetime import datetime, timedelta
+import hashlib
+import logging
+import cPickle
+
+logger = logging.getLogger(__name__)
+
+class Cache(object):
+ DEFAULT_VALIDITY = 60
+ CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
+
+ def __init__(self, owner):
+ self._owner = owner
+
+
+ def __getKey(self, key):
+ import os
+ h = hashlib.md5()
+ h.update(self._owner + key)
+ return h.hexdigest()
+
+ def get(self,skey, defValue = None):
+ now = getSqlDatetime()
+ #logger.debug('Requesting key "%s" for cache "%s"' % (skey, self._owner,))
+ try:
+ key = self.__getKey(skey)
+ c = dbCache.objects.get(pk=key)
+ expired = now > c.created + timedelta(seconds = c.validity)
+ if expired:
+ return defValue
+ val = cPickle.loads(c.value.decode(Cache.CODEC))
+ return val
+ except dbCache.DoesNotExist:
+ logger.debug('key not found')
+ return defValue
+
+ def remove(self,skey):
+ #logger.debug('Removing key "%s" for uService "%s"' % (skey, self._owner))
+ try:
+ key = self.__getKey(skey)
+ dbCache.objects.get(pk=key).delete()
+ except dbCache.DoesNotExist:
+ logger.debug('key not found')
+
+ @transaction.autocommit
+ def put(self, skey, value, validity = None):
+ #logger.debug('Saving key "%s" for cache "%s"' % (skey, self._owner,))
+ if validity == None:
+ validity = Cache.DEFAULT_VALIDITY
+ key = self.__getKey(skey)
+ value = cPickle.dumps(value).encode(Cache.CODEC)
+ now = getSqlDatetime()
+ try:
+ dbCache.objects.create( owner = self._owner, key = key, value = value, created = now, validity = validity )
+ except Exception:
+ # Already exists, modify it
+ c = dbCache.objects.get(pk=key)
+ c.owner = self._owner
+ c.key = key
+ c.value = value
+ c.created = datetime.now()
+ c.validity = validity
+ c.save()
+
+ @transaction.autocommit
+ def refresh(self, skey):
+ #logger.debug('Refreshing key "%s" for cache "%s"' % (skey, self._owner,))
+ try:
+ key = self.__getKey(skey)
+ c = dbCache.objects.get(pk=key)
+ c.created = getSqlDatetime()
+ c.save()
+ except dbCache.DoesNotExist:
+ logger.debug('Can\'t refresh cache key %s because it don\'t exists' % skey)
+ return
+
+ @staticmethod
+ def purge():
+ dbCache.objects.all().delete()
+
+ @staticmethod
+ def cleanUp():
+ dbCache.cleanUp()
+
+ @staticmethod
+ def delete(owner = None):
+ #logger.info("Deleting cache items")
+ if owner == None:
+ objects = dbCache.objects.all()
+ else:
+ objects = dbCache.objects.filter(owner=owner)
+ objects.delete()
+
diff --git a/trunk/server/src/uds/core/util/Config.py b/trunk/server/src/uds/core/util/Config.py
new file mode 100644
index 00000000..8ec8d7b6
--- /dev/null
+++ b/trunk/server/src/uds/core/util/Config.py
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.conf import settings
+from uds.models import Config as dbConfig
+from uds.core.managers.CryptoManager import CryptoManager
+import logging
+
+logger = logging.getLogger(__name__)
+
+GLOBAL_SECTION = 'UDS'
+
+
+class Config:
+ '''
+ Keeps persistend configuration data
+ '''
+
+ class _Value:
+ def __init__(self, section, key, default = '', crypt = False, longText = False):
+ self._section = section
+ self._key = key
+ self._crypt = crypt
+ self._longText = longText
+ if crypt is False:
+ self._default = default
+ else:
+ self._default = CryptoManager.manager().encrypt(default)
+ self._data = None
+
+ def get(self, force = False):
+ try:
+ if force or self._data is None:
+ #logger.debug('Accessing db config {0}.{1}'.format(self._section.name(), self._key))
+ readed = dbConfig.objects.filter(section=self._section.name(), key=self._key)[0]
+ self._data = readed.value
+ self._crypt = [self._crypt, True][readed.crypt] # True has "higher" precedende than False
+ self._longText = readed.long
+ except Exception:
+ # Not found
+ if self._default != '' and self._crypt:
+ self.set( CryptoManager.manager().decrypt(self._default) )
+ elif not self._crypt:
+ self.set(self._default)
+ self._data = self._default
+ if self._crypt is True:
+ return CryptoManager.manager().decrypt(self._data)
+ else:
+ return self._data
+
+ def getInt(self):
+ try:
+ return int(self.get())
+ except Exception:
+ return self._default
+
+ def getBool(self):
+ if self.get() == '0':
+ return False
+ return True
+
+ def key(self):
+ return self._key
+
+ def section(self):
+ return self._section.name()
+
+ def isCrypted(self):
+ return self._crypt
+
+ def isLongText(self):
+ return self._longText
+
+ def set(self, value):
+ if self._crypt is True:
+ value = CryptoManager.manager().encrypt(value)
+ '''
+ Editable here means that this configuration value can be edited by admin directly (generally, that this is a "clean text" value)
+ '''
+ logger.debug('Saving config {0}.{1} as {2}'.format(self._section.name(), self._key, value))
+ try:
+ if dbConfig.objects.filter(section=self._section.name(), key=self._key).update(value=value, crypt=self._crypt, long=self._longText) == 0:
+ raise Exception() # Do not exists, create a new one
+ except Exception:
+ try:
+ dbConfig.objects.create(section=self._section.name(), key=self._key, value=value, crypt=self._crypt, long=self._longText)
+ except Exception:
+ # Probably a migration issue, just ignore it
+ logger.info("Could not save configuration key {0}.{1}".format(self._section.name(), self._key))
+
+ class _Section:
+ def __init__(self, sectionName):
+ self._sectionName = sectionName
+
+ def value(self, key, default = ''):
+ return Config._Value(self, key, default)
+
+ def valueCrypt(self, key, default = ''):
+ return Config._Value(self, key, default, True)
+
+ def valueLong(self, key, default = ''):
+ return Config._Value(self, key, default, False, True)
+
+ def name(self):
+ return self._sectionName
+
+
+
+ @staticmethod
+ def section(sectionName):
+ return Config._Section(sectionName)
+
+ @staticmethod
+ def enumerate():
+ for cfg in dbConfig.objects.all():
+ if cfg.crypt is True:
+ val = Config.section(cfg.section).valueCrypt(cfg.key, CryptoManager.manager().decrypt(cfg.value))
+ else:
+ val = Config.section(cfg.section).value(cfg.key, cfg.value)
+ yield val
+
+ @staticmethod
+ def update(section, key, value):
+ # If cfg value does not exists, simply ignore request
+ try:
+ cfg = dbConfig.objects.filter(section=section, key=key)[0]
+ if cfg.crypt is True:
+ value = CryptoManager.manager().encrypt(value)
+ cfg.value = value
+ cfg.save()
+ logger.debug('Updated value for {0}.{1} to {2}'.format(section, key, value))
+ except Exception:
+ pass
+
+class GlobalConfig:
+ '''
+ Simple helper to keep track of global configuration
+ '''
+ SESSION_EXPIRE_TIME = Config.section(GLOBAL_SECTION).value('sessionExpireTime', '24') # Max session duration (in use) after a new publishment has been made
+ # Delay between cache checks. reducing this number will increase cache generation speed but also will load service providers
+ CACHE_CHECK_DELAY = Config.section(GLOBAL_SECTION).value('cacheCheckDelay', '20')
+ # Delayed task number of threads PER SERVER, with higher number of threads, deplayed task will complete sooner, but it will give more load to overall system
+ DELAYED_TASKS_THREADS = Config.section(GLOBAL_SECTION).value('delayedTasksThreads', '2')
+ # Number of scheduler threads running PER SERVER, with higher number of threads, deplayed task will complete sooner, but it will give more load to overall system
+ SCHEDULER_THREADS = Config.section(GLOBAL_SECTION).value('schedulerThreads', '2')
+ # Waiting time before removing "errored" and "removed" publications, cache, and user assigned machines. Time is in seconds
+ CLEANUP_CHECK = Config.section(GLOBAL_SECTION).value('cleanupCheck', '3600')
+ # Time to maintaing "info state" items before removing it, in seconds
+ KEEP_INFO_TIME = Config.section(GLOBAL_SECTION).value('keepInfoTime', '14400') # Defaults to 2 days 172800?? better 4 hours xd
+ # Max number of services to be "preparing" at same time
+ MAX_PREPARING_SERVICES = Config.section(GLOBAL_SECTION).value('maxPreparingServices', '15') # Defaults to 15 services at once (per service provider)
+ # Max number of service to be at "removal" state at same time
+ MAX_REMOVING_SERVICES = Config.section(GLOBAL_SECTION).value('maxRemovinggServices', '15') # Defaults to 15 services at once (per service provider)
+ # If we ignore limits (max....)
+ IGNORE_LIMITS = Config.section(GLOBAL_SECTION).value('ignoreLimits', '0')
+ # Number of services to initiate removal per run of CacheCleaner
+ USER_SERVICE_CLEAN_NUMBER = Config.section(GLOBAL_SECTION).value('userServiceCleanNumber', '3') # Defaults to 3 per wun
+ # Removal Check time for cache, publications and deployed services
+ REMOVAL_CHECK = Config.section(GLOBAL_SECTION).value('removalCheck', '30') # Defaults to 30 seconds
+ # Login URL
+ LOGIN_URL = Config.section(GLOBAL_SECTION).value('loginUrl', '/login') # Defaults to /login
+ # Session duration
+ USER_SESSION_LENGTH = Config.section(GLOBAL_SECTION).value('userSessionLength', '14400') # Defaults to 4 hours
+ # Superuser (do not need to be at database!!!)
+ SUPER_USER_LOGIN = Config.section(GLOBAL_SECTION).value('superUser', 'root') # Defaults to 4 hours
+ # Superuser password (do not need to be at database!!!)
+ SUPER_USER_PASS = Config.section(GLOBAL_SECTION).valueCrypt('rootPass', 'udsmam0')
+ # Idle time before closing session on admin
+ ADMIN_IDLE_TIME = Config.section(GLOBAL_SECTION).value('adminIdleTime', '14400') # Defaults to 4 hous
+ # Time betwen checks of unused services by os managers
+ # Unused services will be invoked for every machine assigned but not in use AND that has been assigned at least 1/2 of this time
+ CHECK_UNUSED_TIME = Config.section(GLOBAL_SECTION).value('checkUnusedTime', '600') # Defaults to 10 minutes
+ # Default CSS Used
+ CSS = Config.section(GLOBAL_SECTION).value('css', settings.STATIC_URL + 'css/uds.css')
+ # Max logins before blocking an account
+ MAX_LOGIN_TRIES = Config.section(GLOBAL_SECTION).value('maxLoginTries', '3')
+ # Block time in second for an user that makes too many mistakes, 5 minutes default
+ LOGIN_BLOCK = Config.section(GLOBAL_SECTION).value('loginBlockTime', '300')
+ # Do autorun of service if just one service.
+ # 0 = No autorun, 1 = Autorun at login
+ # In a future, maybe necessary another value "2" that means that autorun always
+ AUTORUN_SERVICE = Config.section(GLOBAL_SECTION).value('autorunService', '0')
+ # Redirect HTTP to HTTPS
+ REDIRECT_TO_HTTPS = Config.section(GLOBAL_SECTION).value('redirectToHttps', '0')
+ # Max time needed to get a service "fully functional" before it's considered "failed" and removed
+ # The time is in seconds
+ MAX_INITIALIZING_TIME = Config.section(GLOBAL_SECTION).value('maxInitTime', '3600')
+ # Custom HTML for login page
+ CUSTOM_HTML_LOGIN = Config.section(GLOBAL_SECTION).valueLong('customHtmlLogin', '')
+ # Maximum logs per user service
+ MAX_LOGS_PER_ELEMENT = Config.section(GLOBAL_SECTION).value('maxLogPerElement', '100')
+
+ initDone = False
+
+ @staticmethod
+ def initialize():
+ try:
+ # Tries to initialize database data for global config so it is stored asap and get cached for use
+ GlobalConfig.SESSION_EXPIRE_TIME.get()
+ GlobalConfig.CACHE_CHECK_DELAY.get()
+ GlobalConfig.DELAYED_TASKS_THREADS.get()
+ GlobalConfig.SCHEDULER_THREADS.get()
+ GlobalConfig.CLEANUP_CHECK.get()
+ GlobalConfig.KEEP_INFO_TIME.get()
+ GlobalConfig.MAX_PREPARING_SERVICES.get()
+ GlobalConfig.MAX_REMOVING_SERVICES.get()
+ GlobalConfig.USER_SERVICE_CLEAN_NUMBER.get()
+ GlobalConfig.REMOVAL_CHECK.get()
+ GlobalConfig.LOGIN_URL.get()
+ GlobalConfig.USER_SESSION_LENGTH.get()
+ GlobalConfig.SUPER_USER_LOGIN.get()
+ GlobalConfig.SUPER_USER_PASS.get()
+ GlobalConfig.ADMIN_IDLE_TIME.get()
+ GlobalConfig.CHECK_UNUSED_TIME.get()
+ GlobalConfig.CSS.get()
+ GlobalConfig.MAX_LOGIN_TRIES.get()
+ GlobalConfig.LOGIN_BLOCK.get()
+ GlobalConfig.AUTORUN_SERVICE.get()
+ GlobalConfig.REDIRECT_TO_HTTPS.get()
+ GlobalConfig.MAX_INITIALIZING_TIME.get()
+ GlobalConfig.CUSTOM_HTML_LOGIN.get()
+ GlobalConfig.MAX_LOGS_PER_ELEMENT.get()
+ except:
+ logger.debug('Config table do not exists!!!, maybe we are installing? :-)')
+
+# Context processor
+def context_processor(request):
+ return { 'css_path' : GlobalConfig.CSS.get() }
+
+# Initialization of global configurations
+GlobalConfig.initialize()
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/Decorators.py b/trunk/server/src/uds/core/util/Decorators.py
new file mode 100644
index 00000000..dc1dc6c7
--- /dev/null
+++ b/trunk/server/src/uds/core/util/Decorators.py
@@ -0,0 +1,28 @@
+'''
+Created on Feb 6, 2012
+
+@author: dkmaster
+'''
+
+from time import sleep
+from functools import wraps
+
+# Have to test these decorators before using them
+def retryOnException(retries=3, delay = 0):
+ '''
+ Decorator to retry
+ '''
+ def decorator(func):
+ @wraps(func)
+ def _wrapped_func(*args, **kwargs):
+ while retries > 0:
+ retries -= 1
+ try:
+ return func(*args, **kwargs)
+ except Exception:
+ if retries == 0:
+ raise
+ if delay > 0:
+ sleep(delay)
+ return _wrapped_func
+ return decorator
diff --git a/trunk/server/src/uds/core/util/OsDetector.py b/trunk/server/src/uds/core/util/OsDetector.py
new file mode 100644
index 00000000..3105a886
--- /dev/null
+++ b/trunk/server/src/uds/core/util/OsDetector.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+import re
+import logging
+
+logger = logging.getLogger(__name__)
+
+Linux = 'Linux'
+Windows = 'Windows'
+Macintosh = 'Macintosh'
+
+
+knownOss = { 'Linux' : Linux, 'Windows' : Windows, 'Macintosh' : Macintosh }
+
+def getOsFromUA(ua):
+ '''
+ Basic OS Client detector (very basic indeed :-))
+ '''
+ res = {'OS' : 'Unknown', 'Version' : 'unused' }
+ for k, v in knownOss.iteritems():
+ try:
+ ua.index(v)
+ res['OS'] = k
+ break
+ except Exception:
+ pass
+ logger.debug(res)
+ return res
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/State.py b/trunk/server/src/uds/core/util/State.py
new file mode 100644
index 00000000..a5201879
--- /dev/null
+++ b/trunk/server/src/uds/core/util/State.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+
+from django.utils.translation import ugettext_noop as _, ugettext
+
+# States for different objects. Not all objects supports all States
+class State(object):
+ '''
+ This class represents possible states for objects at database.
+ Take in consideration that objects do not have to support all states, they are here for commodity
+ '''
+ ACTIVE = 'A'
+ INACTIVE = 'I'
+ BLOCKED = 'B'
+ LAUNCHING = 'L'
+ PREPARING = 'P'
+ USABLE = 'U'
+ REMOVABLE = 'R'
+ REMOVING = 'M'
+ REMOVED = 'S'
+ CANCELED = 'C'
+ CANCELING = 'K'
+ ERROR = 'E'
+ RUNNING = 'W'
+ FINISHED = 'F'
+ FOR_EXECUTE = 'X'
+
+ string = { ACTIVE: _('Active'), INACTIVE: _('Inactive'), BLOCKED: _('Blocked'), LAUNCHING: _('Waiting publication'),
+ PREPARING: _('In preparation'), USABLE: _('Valid'),
+ REMOVABLE: _('Waiting for removal'), REMOVING: _('Removing'), REMOVED: _('Removed'), CANCELED: _('Canceled'),
+ CANCELING: _('Canceling'), ERROR: _('Error'), RUNNING: _('Running'), FINISHED: _('Finished'), FOR_EXECUTE: _('Waiting execution') }
+
+ # States that are merely for "information" to the user. They don't contain any usable instance
+ INFO_STATES = [REMOVED, CANCELED, ERROR]
+
+ # States that indicates that the service is "Valid" for a user
+ VALID_STATES = [USABLE,PREPARING]
+
+ # Publication States
+ PUBLISH_STATES = [LAUNCHING, PREPARING]
+
+ @staticmethod
+ def isActive(state):
+ return state == State.ACTIVE
+
+ @staticmethod
+ def isInactive(state):
+ return state == State.INACTIVE
+
+ @staticmethod
+ def isBlocked(state):
+ return state == State.BLOCKED
+
+ @staticmethod
+ def isPreparing(state):
+ return state == State.PREPARING
+
+ @staticmethod
+ def isUsable(state):
+ return state == State.USABLE
+
+ @staticmethod
+ def isRemovable(state):
+ return state == State.REMOVABLE
+
+ @staticmethod
+ def isRemoving(state):
+ return state == State.REMOVING
+
+ @staticmethod
+ def isRemoved(state):
+ return state == State.REMOVED
+
+ @staticmethod
+ def isCanceling(state):
+ return state == State.CANCELING
+
+ @staticmethod
+ def isCanceled(state):
+ return state == State.CANCELED
+
+ @staticmethod
+ def isErrored(state):
+ return state == State.ERROR
+
+ @staticmethod
+ def isFinished(state):
+ return state == State.FINISHED
+
+ @staticmethod
+ def isRuning(state):
+ return state == State.RUNNING
+
+ @staticmethod
+ def isForExecute(state):
+ return state == State.FOR_EXECUTE
+
+ @staticmethod
+ def toString(state):
+ try:
+ return State.string[state]
+ except Exception:
+ return ''
+
diff --git a/trunk/server/src/uds/core/util/StateQueue.py b/trunk/server/src/uds/core/util/StateQueue.py
new file mode 100644
index 00000000..72a7ad76
--- /dev/null
+++ b/trunk/server/src/uds/core/util/StateQueue.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+class StateQueue(object):
+
+ def __init__(self):
+ self.reset()
+
+ def __str__(self):
+ res = '' % (self._current , ','.join( state for state in self._queue ))
+ return res
+
+ def clearQueue(self):
+ self._queue = []
+
+ def reset(self):
+ self._queue = []
+ self._current = None
+
+ def getCurrent(self):
+ return self._current
+
+ def setCurrent(self, newState):
+ self._current = newState
+ return self._current
+
+ def contains(self, state):
+ #if self._queue.co
+ for s in self._queue:
+ if s == state:
+ return True
+ return False
+
+ def push_back(self, state):
+ self._queue.append(state)
+
+ def pop_front(self):
+ if len(self._queue) > 0:
+ return self._queue.pop(0)
+ return None
+
+ def remove(self, state):
+ try:
+ self._queue.remove(state)
+ except Exception:
+ pass # If state not in queue, nothing happens
diff --git a/trunk/server/src/uds/core/util/Storage.py b/trunk/server/src/uds/core/util/Storage.py
new file mode 100644
index 00000000..9449af6b
--- /dev/null
+++ b/trunk/server/src/uds/core/util/Storage.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.models import Storage as dbStorage
+import hashlib
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Storage(object):
+ CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
+
+ def __init__(self, owner):
+ self._owner = owner
+
+ def __getKey(self, key):
+ h = hashlib.md5()
+ h.update(self._owner)
+ h.update(str(key))
+ return h.hexdigest()
+
+
+ def saveData(self, skey, data, attr1 = None):
+ key = self.__getKey(skey)
+ data = data.encode(Storage.CODEC)
+ attr1 = '' if attr1 == None else attr1
+ try:
+ dbStorage.objects.create(owner = self._owner, key = key, data = data, attr1 = attr1 )
+ except Exception:
+ dbStorage.objects.filter(key=key).update(owner = self._owner, data = data, attr1 = attr1)
+ logger.debug('Key saved')
+
+ def put(self, skey, data):
+ return self.saveData(skey, data)
+
+ def updateData(self, skey, data, attr1 = None):
+ self.saveData(skey, data, attr1)
+
+ def readData(self, skey):
+ try:
+ key = self.__getKey(skey)
+ logger.debug('Accesing to {0} {1}'.format(skey, key))
+ c = dbStorage.objects.get(pk=key)
+ return c.data.decode(Storage.CODEC)
+ except dbStorage.DoesNotExist:
+ logger.debug('key not found')
+ return None
+
+ def get(self, skey):
+ return self.readData(skey)
+
+ def remove(self, skey):
+ try:
+ key = self.__getKey(skey)
+ dbStorage.objects.filter(key=key).delete()
+ except Exception:
+ pass
+
+ def lock(self):
+ '''
+ Use with care. If locked, it must be unlocked before returning
+ '''
+ dbStorage.objects.lock()
+
+ def unlock(self):
+ '''
+ Must be used to unlock table
+ '''
+ dbStorage.objects.unlock()
+
+ @staticmethod
+ def delete(owner = None):
+ logger.info("Deleting storage items")
+ if owner == None:
+ objects = dbStorage.objects.all()
+ else:
+ objects = dbStorage.objects.filter(owner=owner)
+ objects.delete()
+
+ def locateByAttr1(self, attr1):
+ res = []
+ for v in dbStorage.objects.filter( attr1 = attr1 ):
+ res.append( v.data.decode(Storage.CODEC) )
+ return res
diff --git a/trunk/server/src/uds/core/util/UniqueIDGenerator.py b/trunk/server/src/uds/core/util/UniqueIDGenerator.py
new file mode 100644
index 00000000..139669d5
--- /dev/null
+++ b/trunk/server/src/uds/core/util/UniqueIDGenerator.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.models import UniqueId as dbUniqueId
+import logging
+
+logger = logging.getLogger(__name__)
+
+class UniqueIDGenerator(object):
+
+ def __init__(self, typeName, owner, baseName = 'uds'):
+ self._owner = owner + typeName
+ self._baseName = baseName
+
+ def setBaseName(self, newBaseName):
+ self._baseName = newBaseName
+
+ def __filter(self, rangeStart):
+ return dbUniqueId.objects.filter( basename = self._baseName, seq__gte=rangeStart )
+
+ def get(self, rangeStart=0, rangeEnd=1000000000):
+ '''
+ Tries to generate a new unique id in the range provided. This unique id
+ is global to "unique ids' database
+ '''
+ # First look for a name in the range defined
+ try:
+ dbUniqueId.objects.lock()
+ flt = self.__filter(rangeStart)
+ try:
+ item = flt.filter(assigned=False)[0]
+ dbUniqueId.objects.filter(id=item.id).update( owner = self._owner, assigned = True )
+ seq = item.seq
+ except Exception, e: # No free element found
+ try:
+ last = flt.filter(assigned = True)[0] # DB Returns correct order so the 0 item is the last
+ seq = last.seq + 1
+ except Exception: # If there is no assigned at database
+ seq = rangeStart
+ logger.debug('Found seq {0}'.format(seq))
+ if seq > rangeEnd:
+ return None # No ids free in range
+ dbUniqueId.objects.create( owner = self._owner, basename = self._baseName, seq = seq, assigned = True)
+ return seq
+ except Exception:
+ logger.exception('Generating unique id sequence')
+ return None
+ finally:
+ dbUniqueId.objects.unlock()
+
+ def free(self, seq):
+ try:
+ logger.debug('Freeing seq {0} from {1} ({2})'.format(seq, self._owner, self._baseName))
+ dbUniqueId.objects.lock()
+ flt = self.__filter(0).filter(owner = self._owner, seq=seq).update(owner='', assigned=False)
+ if flt > 0:
+ self.__purge()
+ finally:
+ dbUniqueId.objects.unlock()
+
+
+ def __purge(self):
+ try:
+ last = self.__filter(0).filter(assigned=True)[0]
+ seq = last.seq+1
+ except:
+ seq = 0
+ self.__filter(seq).delete() # Clean ups all unassigned after last assigned in this range
+
+
+ def release(self):
+ try:
+ dbUniqueId.objects.lock()
+ dbUniqueId.objects.filter(owner=self._owner).update(assigned=False, owner='')
+ self.__purge()
+ finally:
+ dbUniqueId.objects.unlock()
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/UniqueMacGenerator.py b/trunk/server/src/uds/core/util/UniqueMacGenerator.py
new file mode 100644
index 00000000..4f97bfb7
--- /dev/null
+++ b/trunk/server/src/uds/core/util/UniqueMacGenerator.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from UniqueIDGenerator import UniqueIDGenerator
+import logging, re
+
+logger = logging.getLogger(__name__)
+
+class UniqueMacGenerator(UniqueIDGenerator):
+
+ def __init__(self, owner):
+ super(UniqueMacGenerator, self).__init__('mac', owner, '\tmac')
+
+ def __toInt(self, mac):
+ return int(mac.replace(':', ''), 16)
+
+ def __toMac(self, seq):
+ return re.sub(r"(..)", r"\1:", "%0*X" % (12, seq))[:-1]
+
+ def get(self, macRange):
+ firstMac, lastMac = macRange.split('-')
+ firstMac = self.__toInt(firstMac)
+ lastMac = self.__toInt(lastMac)
+ return self.__toMac(super(UniqueMacGenerator, self).get(firstMac, lastMac))
+
+ def free(self, mac):
+ super(UniqueMacGenerator, self).free( self.__toInt(mac) )
+
+ # Release is inherited, no mod needed
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/UniqueNameGenerator.py b/trunk/server/src/uds/core/util/UniqueNameGenerator.py
new file mode 100644
index 00000000..ec9ab20c
--- /dev/null
+++ b/trunk/server/src/uds/core/util/UniqueNameGenerator.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from UniqueIDGenerator import UniqueIDGenerator
+import logging
+
+logger = logging.getLogger(__name__)
+
+class UniqueNameGenerator(UniqueIDGenerator):
+
+ def __init__(self, owner):
+ super(UniqueNameGenerator, self).__init__('name', owner, )
+
+ def __toName(self, seq, length):
+ return "%s%0*d" % (self._baseName, length, seq)
+
+ def get(self, baseName, length=5):
+ self.setBaseName(baseName)
+ minVal = 0
+ maxVal = 10**length - 1
+ return self.__toName(super(UniqueNameGenerator, self).get(minVal, maxVal), length)
+
+
+ def free(self, baseName, name):
+ self.setBaseName(baseName)
+ super(UniqueNameGenerator, self).free(int(name[len(self._baseName):]))
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/__init__.py b/trunk/server/src/uds/core/util/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/core/util/connection.py b/trunk/server/src/uds/core/util/connection.py
new file mode 100644
index 00000000..436049c4
--- /dev/null
+++ b/trunk/server/src/uds/core/util/connection.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+import socket
+
+logger = logging.getLogger(__name__)
+
+def testServer(host, port, timeOut = 4):
+ try:
+ logger.debug('Checking connection to {0}:{1} with {2} seconds timeout'.format(host, port, timeOut))
+ sock = socket.create_connection((host, port), timeOut)
+ sock.close()
+ except Exception, e:
+ logger.debug('Exception checking {0}:{1} with {2} timeout: {3}'.format(host, port, timeOut, e))
+ return False
+ return True
+
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/db/LockingManager.py b/trunk/server/src/uds/core/util/db/LockingManager.py
new file mode 100644
index 00000000..ea3bb344
--- /dev/null
+++ b/trunk/server/src/uds/core/util/db/LockingManager.py
@@ -0,0 +1,73 @@
+'''
+From django locking manager snippet at http://djangosnippets.org/snippets/833/
+Author:
+ miohtama
+'''
+
+from django.db import models, connection
+import logging
+
+logger = logging.getLogger(__name__)
+
+# Table locking in mysql at least is based on thread requesting it, so we should not have problems with a bit of care
+class LockingManager(models.Manager):
+ """ Add lock/unlock functionality to manager.
+
+ Example::
+
+ class Job(models.Model):
+
+ manager = LockingManager()
+
+ counter = models.IntegerField(null=True, default=0)
+
+ @staticmethod
+ def do_atomic_update(job_id)
+ ''' Updates job integer, keeping it below 5 '''
+ try:
+ # Ensure only one HTTP request can do this update at once.
+ Job.objects.lock()
+
+ job = Job.object.get(id=job_id)
+ # If we don't lock the tables two simultanous
+ # requests might both increase the counter
+ # going over 5
+ if job.counter < 5:
+ job.counter += 1
+ job.save()
+
+ finally:
+ Job.objects.unlock()
+
+
+ """
+
+ def lock(self):
+ """ Lock table.
+
+ Locks the object model table so that atomic update is possible.
+ Simulatenous database access request pend until the lock is unlock()'ed.
+
+ Note: If you need to lock multiple tables, you need to do lock them
+ all in one SQL clause and this function is not enough. To avoid
+ dead lock, all tables must be locked in the same order.
+
+ See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
+ """
+ con = connection
+ cursor = con.cursor()
+ table = self.model._meta.db_table
+ #logger.debug("Locking table %s" % table)
+ cursor.execute("LOCK TABLES %s WRITE" % table)
+ row = cursor.fetchone()
+ return row
+
+ def unlock(self):
+ """ Unlock the table. """
+ #logger.debug("Unlocked tables")
+ con = connection
+ cursor = con.cursor()
+ #table = self.model._meta.db_table
+ cursor.execute("UNLOCK TABLES")
+ row = cursor.fetchone()
+ return row
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/db/__init__.py b/trunk/server/src/uds/core/util/db/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/core/util/log.py b/trunk/server/src/uds/core/util/log.py
new file mode 100644
index 00000000..99622fd5
--- /dev/null
+++ b/trunk/server/src/uds/core/util/log.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+import logging
+
+logger = logging.getLogger(__name__)
+useLogger = logging.getLogger('useLog')
+
+# Logging levels
+OTHER,DEBUG,INFO,WARN,ERROR,FATAL = (10000*(x+1) for x in xrange(6))
+
+# Logging sources
+INTERNAL,ACTOR,TRANSPORT, OSMANAGER, UNKNOWN, WEB = ('internal', 'actor', 'transport', 'osmanager', 'unknown', 'web')
+
+OTHERSTR,DEBUGSTR,INFOSTR,WARNSTR,ERRORSTR,FATALSTR = ('OTHER', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL')
+
+# Names for defined log levels
+__nameLevels = {
+ DEBUGSTR: DEBUG,
+ INFOSTR: INFO,
+ WARNSTR: WARN,
+ ERRORSTR: ERROR,
+ FATALSTR: FATAL,
+ OTHERSTR: OTHER
+ }
+
+# Reverse dict of names
+__valueLevels = dict((v,k) for k, v in __nameLevels.iteritems())
+
+def logLevelFromStr(str_):
+ '''
+ Gets the numeric log level from an string.
+ '''
+ return __nameLevels.get(str_.upper(), OTHER)
+
+def logStrFromLevel(level):
+ return __valueLevels.get(level, 'OTHER')
+
+
+def useLog(type_, serviceUniqueId, serviceIp, username):
+ useLogger.info('|'.join([type_, serviceUniqueId, serviceIp, username]))
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/util/modfinder.py b/trunk/server/src/uds/core/util/modfinder.py
new file mode 100644
index 00000000..eb4773f5
--- /dev/null
+++ b/trunk/server/src/uds/core/util/modfinder.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import os.path, pkgutil
+import sys, imp
+import logging
+import uds.dispatchers
+
+logger = logging.getLogger(__name__)
+
+
+patterns = []
+
+def loadModulesUrls():
+ logger.debug('Looking for dispatching modules')
+ global patterns
+ if len(patterns) == 0:
+ try:
+ modName = 'uds.dispatchers'
+ pkgpath = os.path.dirname(sys.modules[modName].__file__)
+ for _, name, _ in pkgutil.iter_modules([pkgpath]):
+ fullModName = '%s.%s.urls' % (modName, name)
+ mod = __import__(fullModName, globals(), locals(), ['urlpatterns'], -1)
+ patterns += mod.urlpatterns
+ except Exception, e:
+ logger.debug(e)
+ pass
+
+ return patterns
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/workers/AssignedAndUnused.py b/trunk/server/src/uds/core/workers/AssignedAndUnused.py
new file mode 100644
index 00000000..5be83d6c
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/AssignedAndUnused.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.util.Config import GlobalConfig
+from uds.models import DeployedService, getSqlDatetime
+from uds.core.util.State import State
+from uds.core.jobs.Job import Job
+from datetime import timedelta
+import logging
+
+logger = logging.getLogger(__name__)
+
+class AssignedAndUnused(Job):
+ frecuency = GlobalConfig.CHECK_UNUSED_TIME.getInt()
+
+ def __init__(self, environment):
+ super(AssignedAndUnused,self).__init__(environment)
+
+ def run(self):
+ for ds in DeployedService.objects.all():
+ osm = ds.osmanager.getInstance()
+ if osm.processUnusedMachines is True:
+ logger.debug('Processing unused machines for {0}'.format(osm))
+ since_state = getSqlDatetime() - timedelta( seconds = GlobalConfig.CHECK_UNUSED_TIME.getInt() / 2 )
+ for us in ds.assignedUserServices().select_for_update().filter(in_use=False,since_state__lt=since_state):
+ logger.debug('Found unused assigned service {0}'.format(us))
+ osm.processUnused(us)
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/workers/CacheCleaner.py b/trunk/server/src/uds/core/workers/CacheCleaner.py
new file mode 100644
index 00000000..fc6fb90f
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/CacheCleaner.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.util.Cache import Cache
+from uds.core.jobs.Job import Job
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class CacheCleaner(Job):
+
+ frecuency = 3600*24 # Once a day
+
+ def __init__(self, environment):
+ super(CacheCleaner,self).__init__(environment)
+
+ def run(self):
+ logger.debug('Starting cache cleanup')
+ Cache.cleanUp()
+ logger.debug('Done cache cleanup')
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/workers/DeployedServiceCleaner.py b/trunk/server/src/uds/core/workers/DeployedServiceCleaner.py
new file mode 100644
index 00000000..2e26ac48
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/DeployedServiceCleaner.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import transaction
+from uds.core.util.Config import GlobalConfig
+from uds.models import DeployedService, getSqlDatetime
+from uds.core.util.State import State
+from uds.core.jobs.Job import Job
+from datetime import timedelta
+import logging
+
+logger = logging.getLogger(__name__)
+
+class DeployedServiceInfoItemsCleaner(Job):
+ frecuency = GlobalConfig.CLEANUP_CHECK.getInt() # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(DeployedServiceInfoItemsCleaner,self).__init__(environment)
+
+ def run(self):
+ removeFrom = getSqlDatetime() - timedelta(seconds = GlobalConfig.KEEP_INFO_TIME.getInt())
+ DeployedService.objects.filter(state__in=State.INFO_STATES, state_date__lt=removeFrom).delete()
+
+
+class DeployedServiceRemover(Job):
+ frecuency = GlobalConfig.REMOVAL_CHECK.getInt() # Request run publication "removal" every configued seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(DeployedServiceRemover,self).__init__(environment)
+
+ @transaction.commit_on_success
+ def startRemovalOf(self, ds):
+ # Get publications in course...., can be at most 1!!!
+
+ publishing = ds.publications.filter(state=State.PREPARING)
+ for p in publishing:
+ p.cancel()
+ # Now all publishments are canceling, let's try to cancel cache and assigned
+ uServices = ds.userServices.filter(state=State.PREPARING)
+ for u in uServices:
+ u.cancel()
+ # Nice start of removal, maybe we need to do some limitation later, but there should not be too much services nor publications cancelable at once
+ ds.state = State.REMOVING
+ ds.name = ds.name + ' (removed)'
+ ds.save()
+
+
+ @transaction.commit_on_success
+ def continueRemovalOf(self, ds):
+ # First, we remove all publications and user services in "info_state"
+ ds.userServices.select_for_update().filter(state__in=State.INFO_STATES).delete()
+ # Mark usable user services as removable
+ ds.userServices.select_for_update().filter(state=State.USABLE).update(state=State.REMOVABLE)
+
+ # When no service is at database, we start with publications
+ if ds.userServices.all().count() == 0:
+ try:
+ logger.debug('All services removed, checking active publication')
+ if ds.activePublication() is not None:
+ logger.debug('Active publication found, unpublishing it')
+ ds.unpublish()
+ else:
+ logger.debug('No active publication found, removing info states and checking if removal is done')
+ ds.publications.filter(state__in=State.INFO_STATES).delete()
+ if ds.publications.count() is 0:
+ ds.removed() # Mark it as removed, clean later from database
+ except Exception as e:
+ logger.exception('Cought unexpected exception at continueRemovalOf: ')
+
+ def run(self):
+ # First check if there is someone in "removable" estate
+ rems = DeployedService.objects.filter(state=State.REMOVABLE)[:10]
+ if len(rems) > 0:
+ logger.debug('Found a deployed service marked for removal. Starting removal of {0}'.format(rems))
+ for rem in rems:
+ self.startRemovalOf(rem)
+ rems = DeployedService.objects.filter(state=State.REMOVING)[:10]
+ if len(rems) > 0:
+ logger.debug('Found a deployed service in removing state, continuing removal of {0}'.format(rems))
+ for rem in rems:
+ self.continueRemovalOf(rem)
+
+
diff --git a/trunk/server/src/uds/core/workers/PublicationCleaner.py b/trunk/server/src/uds/core/workers/PublicationCleaner.py
new file mode 100644
index 00000000..a3e123b9
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/PublicationCleaner.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.managers.PublicationManager import PublicationManager
+from uds.core.util.Config import GlobalConfig
+from uds.models import DeployedServicePublication, DeployedService, getSqlDatetime
+from uds.core.services.Exceptions import PublishException
+from uds.core.util.State import State
+from uds.core.jobs.Job import Job
+from datetime import timedelta
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class PublicationInfoItemsCleaner(Job):
+ frecuency = GlobalConfig.CLEANUP_CHECK.getInt() # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(PublicationInfoItemsCleaner,self).__init__(environment)
+
+ def run(self):
+ removeFrom = getSqlDatetime() - timedelta(seconds = GlobalConfig.KEEP_INFO_TIME.getInt())
+ DeployedServicePublication.objects.filter(state__in=State.INFO_STATES, state_date__lt=removeFrom).delete()
+
+class PublicationCleaner(Job):
+ frecuency = GlobalConfig.REMOVAL_CHECK.getInt() # Request run publication "removal" every configued seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(PublicationCleaner,self).__init__(environment)
+
+ def run(self):
+ removables = DeployedServicePublication.objects.filter(state=State.REMOVABLE)[0:3]
+ for removable in removables:
+ try:
+ PublicationManager.manager().unpublish(removable)
+ except PublishException: # Can say that it cant be removed right now
+ logger.debug('Delaying removal')
+ pass
+
+
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/core/workers/ServiceCacheUpdater.py b/trunk/server/src/uds/core/workers/ServiceCacheUpdater.py
new file mode 100644
index 00000000..b35394c8
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/ServiceCacheUpdater.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import transaction
+from django.db.models import Q
+from uds.core.util.Config import GlobalConfig
+from uds.core.util.State import State
+from uds.core.managers.UserServiceManager import UserServiceManager
+from uds.core.services.Exceptions import MaxServicesReachedException
+from uds.models import DeployedService
+from uds.core import services
+from uds.core.jobs.Job import Job
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class ServiceCacheUpdater(Job):
+ '''
+ Cache updater is responsible of keeping up to date the cache for different deployed services configurations requested
+ We only process items that are "cacheables", to speed up process we will use the fact that initialServices = preparedServices = maxServices = 0
+ if cache is not needed.
+ This is included as a scheduled task that will run every X seconds, and scheduler will keep it so it will be only executed by one backend at a time
+ '''
+ frecuency = GlobalConfig.CACHE_CHECK_DELAY.getInt() # Request run cache manager every configured seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(ServiceCacheUpdater,self).__init__(environment)
+
+ @staticmethod
+ def calcProportion(max, actual):
+ return actual * 10000 / max
+
+ def bestDeployedServiceNeedingCacheUpdate(self):
+ # State filter for cached and inAssigned objects
+ # First we get all deployed services that could need cache generation
+ DeployedService.objects.update()
+ # We start filtering out the deployed services that do not need caching at all.
+ whichNeedsCaching = DeployedService.objects.filter(Q(initial_srvs__gt=0) | Q(cache_l1_srvs__gt=0)).filter(max_srvs__gt=0,state=State.ACTIVE)
+
+ # We will get the one that proportionally needs more cache
+ selected = None
+ cachedL1, cachedL2, assigned = 0,0,0
+ toCacheL1 = False # Mark for prefering update L1 cache before L2 cache
+ prop = ServiceCacheUpdater.calcProportion(1,1)
+ for ds in whichNeedsCaching:
+ ds.userServices.update() # Cleans cached queries
+ # If this deployedService don't have a publication active and needs it, ignore it
+ if ds.activePublication() == None and ds.service.getInstance().publicationType is not None:
+ logger.debug('Needs publication but do not have one, cache test ignored')
+ continue
+ # If it has any running publication, do not generate cache anymore
+ if ds.publications.filter(state=State.PREPARING).count() > 0:
+ logger.debug('Stopped cache generation for deployed service with publication running: {0}'.format(ds))
+ continue
+
+ # Get data related to actual state of cache
+ inCacheL1 = ds.cachedUserServices().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L1_CACHE)).count()
+ inCacheL2 = ds.cachedUserServices().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L2_CACHE)).count()
+ inAssigned = ds.assignedUserServices().filter(UserServiceManager.getStateFilter()).count()
+ # if we bypasses max cache, we will reduce it in first place. This is so because this will free resources on service provider
+ logger.debug("Examining {0} with {1} in cache L1 and {2} in cache L2, {3} inAssigned".format(
+ ds, inCacheL1, inCacheL2, inAssigned))
+ totalL1Assigned = inCacheL1 + inAssigned
+ # We have more than we want
+ if totalL1Assigned > ds.max_srvs:
+ logger.debug('We have more services than max configured')
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ selected = ds
+ break
+ # We have more in L1 cache than needed
+ if totalL1Assigned > ds.initial_srvs and inCacheL1 > ds.cache_l1_srvs:
+ logger.debug('We have more services in cache L1 than configured')
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ selected = ds
+ break
+
+ # If we have more in L2 cache than needed, decrease L2 cache, but int this case, we continue checking cause L2 cache removal
+ # has less priority than l1 creations or removals, but higher. In this case, we will simply take last l2 oversized found and reduce it
+ if inCacheL2 > ds.cache_l2_srvs:
+ if toCacheL1 == False:
+ logger.debug('We have more services in L2 cache than configured, decreasing it')
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ selected = ds
+ prop = ServiceCacheUpdater.calcProportion(1,0)
+
+ # If this service don't allows more starting user services, continue
+ if UserServiceManager.manager().canInitiateServiceFromDeployedService(ds) is False:
+ logger.debug('This provider has the max allowed starting services running: {0}'.format(ds))
+ continue
+
+ # If wee need to grow l2 cache, annotate it
+ # Whe check this before checking the total, because the l2 cache is independent of max services or l1 cache.
+ # It reflects a value that must be keeped in cache for futre fast use.
+ if inCacheL2 < ds.cache_l2_srvs:
+ p = ServiceCacheUpdater.calcProportion(ds.cache_l2_srvs, inCacheL2)
+ if p < prop and toCacheL1 == False:
+ logger.debug("Found best for cache until now comparing cache L2: {0}, {1} < {2}".format(ds, p, prop))
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ selected = ds
+ prop = p
+
+ # We skip it if already at max
+ if totalL1Assigned == ds.max_srvs:
+ continue;
+
+ if totalL1Assigned < ds.initial_srvs:
+ p = ServiceCacheUpdater.calcProportion(ds.initial_srvs, totalL1Assigned)
+ if p < prop or toCacheL1 == False:
+ logger.debug("Found best for cache until now comparing initial: {0}, {1} < {2}".format(ds, p, prop))
+ toCacheL1 = True
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ selected = ds
+ prop = p
+ if inCacheL1 < ds.cache_l1_srvs:
+ p = ServiceCacheUpdater.calcProportion(ds.cache_l1_srvs, inCacheL1)
+ if p < prop or toCacheL1 == False:
+ logger.debug("Found best for cache until now comparing prepared: {0}, {1} < {2}".format(ds, p, prop))
+ toCacheL1 = True
+ selected = ds
+ cachedL1, cachedL2, assigned = inCacheL1, inCacheL2, inAssigned
+ prop = p
+
+ # We also return calculated values so we can reuse then
+ return selected, cachedL1, cachedL2, assigned
+
+ @transaction.autocommit
+ def growL1Cache(self, ds, cacheL1, cacheL2, assigned):
+ '''
+ This method tries to enlarge L1 cache.
+
+ If for some reason the number of deployed services (Counting all, ACTIVE
+ and PREPARING, assigned, L1 and L2) is over max allowed service deployments,
+ this method will not grow the L1 cache
+ '''
+ logger.debug("Growing L1 cache creating a new service for {0}".format(ds))
+ # First, we try to assign from L2 cache
+ if cacheL2 > 0:
+ cache = ds.cachedUserServices().select_for_update().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L2_CACHE)).order_by('creation_date')[0]
+ cache.moveToLevel(services.UserDeployment.L1_CACHE)
+ else:
+ try:
+ UserServiceManager.manager().createCacheFor(ds.activePublication(), services.UserDeployment.L1_CACHE)
+ except MaxServicesReachedException as e:
+ logger.error(str(e))
+ # TODO: When alerts are ready, notify this
+
+ @transaction.autocommit
+ def growL2Cache(self, ds, cacheL1, cacheL2, assigned):
+ '''
+ Tries to grow L2 cache of service.
+
+ If for some reason the number of deployed services (Counting all, ACTIVE
+ and PREPARING, assigned, L1 and L2) is over max allowed service deployments,
+ this method will not grow the L1 cache
+ '''
+ logger.debug("Growing L2 cache creating a new service for {0}".format(ds))
+ try:
+ UserServiceManager.manager().createCacheFor(ds.activePublication(), services.UserDeployment.L2_CACHE)
+ except MaxServicesReachedException as e:
+ logger.error(str(e))
+ # TODO: When alerts are ready, notify this
+
+ def reduceL1Cache(self, ds, cacheL1, cacheL2, assigned):
+ logger.debug("Reducing L1 cache erasing a service in cache for {0}".format(ds))
+ # We will try to destroy the newest cacheL1 element that is USABLE if the deployer can't cancel a new service creation
+ cacheItems = ds.cachedUserServices().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L1_CACHE)).order_by('-creation_date')
+ if len(cacheItems) == 0:
+ logger.debug('There is more services than configured, but could not reduce cache cause its already empty')
+ return
+
+ if cacheL2 < ds.cache_l2_srvs:
+ cacheItems[0].moveToLevel(services.UserDeployment.L2_CACHE)
+ else:
+ # TODO: Look first for non finished cache items and cancel them
+ cache = cacheItems[0]
+ cache.removeOrCancel()
+
+ def reduceL2Cache(self, ds, cacheL1, cacheL2, assigned):
+ logger.debug("Reducing L2 cache erasing a service in cache for {0}".format(ds))
+ if cacheL2 > 0:
+ cacheItems = ds.cachedUserServices().filter(UserServiceManager.getCacheStateFilter(services.UserDeployment.L2_CACHE)).order_by('creation_date')
+ # TODO: Look first for non finished cache items and cancel them
+ cache = cacheItems[0]
+ cache.removeOrCancel()
+
+ def run(self):
+ logger.debug('Starting cache checking')
+ # We need to get
+ ds, cacheL1, cacheL2, assigned = self.bestDeployedServiceNeedingCacheUpdate()
+ # We have cache to update??
+ if ds == None:
+ logger.debug('Cache up to date')
+ return
+ logger.debug("Updating cache for {0}".format(ds))
+ totalL1Assigned = cacheL1 + assigned
+
+ # We try first to reduce cache before tring to increase it.
+ # This means that if there is excesive number of user deployments
+ # for L1 or L2 cache, this will be reduced untill they have good numbers.
+ # This is so because service can have limited the number of services and,
+ # if we try to increase cache before having reduced whatever needed
+ # first, the service will get lock until someone removes something.
+ if totalL1Assigned > ds.max_srvs:
+ self.reduceL1Cache(ds, cacheL1, cacheL2, assigned)
+ elif totalL1Assigned > ds.initial_srvs and cacheL1 > ds.cache_l1_srvs:
+ self.reduceL1Cache(ds, cacheL1, cacheL2, assigned)
+ elif cacheL2 > ds.cache_l2_srvs: # We have excesives L2 items
+ self.reduceL2Cache(ds, cacheL1, cacheL2, assigned)
+ elif totalL1Assigned < ds.max_srvs and (totalL1Assigned < ds.initial_srvs or cacheL1 < ds.cache_l1_srvs): # We need more services
+ self.growL1Cache(ds, cacheL1, cacheL2, assigned)
+ elif cacheL2 < ds.cache_l2_srvs: # We need more L2 items
+ self.growL2Cache(ds, cacheL1, cacheL2, assigned)
+ else:
+ logger.info("We have more services than max requested for {0}, but can't erase any of then cause all of them are already assigned".format(ds))
diff --git a/trunk/server/src/uds/core/workers/UserServiceCleaner.py b/trunk/server/src/uds/core/workers/UserServiceCleaner.py
new file mode 100644
index 00000000..6247831b
--- /dev/null
+++ b/trunk/server/src/uds/core/workers/UserServiceCleaner.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import transaction
+from uds.core.managers.UserServiceManager import UserServiceManager
+from uds.core.util.Config import GlobalConfig
+from uds.models import UserService, getSqlDatetime, State
+from uds.core.jobs.Job import Job
+from datetime import timedelta
+import logging
+
+logger = logging.getLogger(__name__)
+
+# Notas:
+# Clean cache info items. DONE
+# Initiate removal of "removable" cached items, with a limit (at most X per run). DONE
+# Look for non current cache items and mark them as removables.
+
+
+
+class UserServiceInfoItemsCleaner(Job):
+ frecuency = GlobalConfig.CLEANUP_CHECK.getInt() # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload
+
+ def __init__(self, environment):
+ super(UserServiceInfoItemsCleaner,self).__init__(environment)
+
+ @transaction.commit_on_success
+ def run(self):
+ removeFrom = getSqlDatetime() - timedelta(seconds = GlobalConfig.KEEP_INFO_TIME.getInt())
+ logger.debug('Removing information user services from {0}'.format(removeFrom))
+ UserService.objects.select_for_update().filter(state__in=State.INFO_STATES, state_date__lt=removeFrom).delete()
+
+
+class UserServiceRemover(Job):
+ frecuency = GlobalConfig.REMOVAL_CHECK.getInt() # Request run cache "info" cleaner every configued seconds. If config value is changed, it will be used at next reload
+ removeAtOnce = GlobalConfig.USER_SERVICE_CLEAN_NUMBER.getInt() # Same, it will work at reload
+
+ def __init__(self, environment):
+ super(UserServiceRemover,self).__init__(environment)
+
+ @transaction.commit_on_success
+ def run(self):
+ removeFrom = getSqlDatetime() - timedelta(seconds=10) # We keep at least 30 seconds the machine before removing it, so we avoid connections errors
+ removables = UserService.objects.filter(state=State.REMOVABLE, state_date__lt=removeFrom)[0:UserServiceRemover.removeAtOnce]
+ for us in removables:
+ UserServiceManager.manager().remove(us)
diff --git a/trunk/server/src/uds/core/workers/__init__.py b/trunk/server/src/uds/core/workers/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/dispatchers/__init__.py b/trunk/server/src/uds/dispatchers/__init__.py
new file mode 100644
index 00000000..3c174982
--- /dev/null
+++ b/trunk/server/src/uds/dispatchers/__init__.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+import logging
+
+logger = logging.getLogger(__name__)
+'''
+Service modules for uds are contained inside this package.
+To create a new service module, you will need to follow this steps:
+ 1.- Create the service module, probably based on an existing one
+ 2.- Insert the module package as child of this package
+ 3.- Import the class of your service module at __init__. For example::
+ from Service import SimpleService
+ 4.- Done. At Server restart, the module will be recognized, loaded and treated
+
+The registration of modules is done locating subclases of :py:class:`uds.core.auths.Authentication`
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+def __init__():
+ '''
+ This imports all packages that are descendant of this package, and, after that,
+ it register all subclases of service provider as
+ '''
+ import os.path, pkgutil
+ import sys
+
+ # Dinamycally import children of this package. The __init__.py files must register, if needed, inside ServiceProviderFactory
+ pkgpath = os.path.dirname(sys.modules[__name__].__file__)
+ for _, name, _ in pkgutil.iter_modules([pkgpath]):
+ __import__(name, globals(), locals(), [], -1)
+
+
+ logger.debug('Dispatchers initialized')
+
+__init__()
diff --git a/trunk/server/src/uds/dispatchers/pam/__init__.py b/trunk/server/src/uds/dispatchers/pam/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/dispatchers/pam/urls.py b/trunk/server/src/uds/dispatchers/pam/urls.py
new file mode 100644
index 00000000..152466bb
--- /dev/null
+++ b/trunk/server/src/uds/dispatchers/pam/urls.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.conf.urls.defaults import patterns, include
+
+urlpatterns = patterns(__package__,
+ (r'^pam$', 'views.pam'),
+ )
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/dispatchers/pam/views.py b/trunk/server/src/uds/dispatchers/pam/views.py
new file mode 100644
index 00000000..8992b65e
--- /dev/null
+++ b/trunk/server/src/uds/dispatchers/pam/views.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.http import HttpResponseNotAllowed, HttpResponse
+from uds.core.util.Cache import Cache
+import logging
+
+logger = logging.getLogger(__name__)
+
+# We will use the cache to "hold" the tickets valid for users
+
+# Create your views here.
+def pam(request):
+ response = ''
+ cache = Cache('pam')
+ if request.method == 'POST':
+ return HttpResponseNotAllowed(['GET'])
+ if request.GET.has_key('id') & request.GET.has_key('pass'):
+ # This is an "auth" request
+ logger.debug("Auth request for user [{0}] and pass [{1}]".format(request.GET['id'], request.GET['pass']))
+ password = cache.get(request.GET['id'])
+ response = '0'
+ if password == request.GET['pass']:
+ response = '1'
+ cache.remove(request.GET['id']) # Ticket valid for just 1 login
+
+ elif request.GET.has_key('uid'):
+ # This is an "get name for id" call
+ logger.debug("NSS Request for id [{0}]".format(request.GET['uid']))
+ response = '10000 udstmp'
+ elif request.GET.has_key('name'):
+ logger.debug("NSS Request for username [{0}]".format(request.GET['name']))
+ response = '10000 udstmp'
+
+ return HttpResponse(response, content_type='text/plain')
diff --git a/trunk/server/src/uds/locale/de/LC_MESSAGES/django.mo b/trunk/server/src/uds/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..2b55f942
Binary files /dev/null and b/trunk/server/src/uds/locale/de/LC_MESSAGES/django.mo differ
diff --git a/trunk/server/src/uds/locale/de/LC_MESSAGES/django.po b/trunk/server/src/uds/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 00000000..22b65996
--- /dev/null
+++ b/trunk/server/src/uds/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,1851 @@
+# Translations for german
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# Adolfo Gómez , 2012.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-28 10:17+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/OVirt/OVirtProvider.py:91
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "Host"
+msgstr "Host"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "VMWare VC Server IP or Hostname"
+msgstr "VMWare VC Server IP oder Hostname"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid "Use SSL"
+msgstr "Verwendung SSL"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+msgid "If checked, will use a ssl connection to Active Directory"
+msgstr ""
+"Wenn diese Option aktiviert ist, verwendet eine Ssl-Verbindung zum Ldap "
+"(Wenn Port 389 ist, wird in Tatsache Port 636)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Ldap User"
+msgstr "LDAP-Benutzer"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+msgid ""
+"Username with read privileges on the base selected (use USER@DOMAIN.DOM form "
+"for this)"
+msgstr "Benutzernamen mit lesen Berechtigungen auf der Basis ausgewählt"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/ActiveDirectory_enterprise/Authenticator.py:50
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/RegexLdap/Authenticator.py:76
+#: auths/SimpleLDAP/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:77
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+#: services/OVirt/OVirtProvider.py:93
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67 web/forms/LoginForm.py:58
+msgid "Password"
+msgstr "Passwort"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:53
+msgid "Password of the ldap user"
+msgstr "Kennwort für den Ldap-Benutzer"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout"
+msgstr "Timeout"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+msgid "Timeout in seconds of connection to LDAP"
+msgstr "Timeout in Sekunden über LDAP-Verbindung"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:35
+msgid "Active Directory Authenticator"
+msgstr "Active Directory-Authenticator"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:37
+#, fuzzy
+msgid "Authenticate against Active Directory"
+msgstr "Authentifikator mit Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:46
+#: auths/EDirectory_enterprise/Authenticator.py:77
+#: auths/RegexLdap/Authenticator.py:72 auths/SimpleLDAP/Authenticator.py:73
+#: services/OVirt/OVirtProvider.py:92
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66 web/forms/LoginForm.py:57
+msgid "Username"
+msgstr "Benutzername"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:48
+#: auths/EDirectory_enterprise/Authenticator.py:79
+#: auths/RegexLdap/Authenticator.py:74 auths/SAML_enterprise/SAML.py:113
+#: auths/SimpleLDAP/Authenticator.py:75
+msgid "Group"
+msgstr "Gruppe"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:61
+#: auths/ActiveDirectory_enterprise/Authenticator.py:395
+msgid "Must specify the username in the form USERNAME@DOMAIN.DOM"
+msgstr "Müssen den Benutzernamen in der Form Benutzername@Domäne angeben.DOM"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:127
+#: auths/EDirectory_enterprise/Authenticator.py:123
+#: auths/RegexLdap/Authenticator.py:159 auths/SimpleLDAP/Authenticator.py:158
+msgid "Ldap connection error: "
+msgstr "LDAP-Verbindungsfehler: "
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:299
+#: auths/ActiveDirectory_enterprise/Authenticator.py:345
+#: auths/EDirectory_enterprise/Authenticator.py:243
+#: auths/EDirectory_enterprise/Authenticator.py:286
+#: auths/RegexLdap/Authenticator.py:249 auths/RegexLdap/Authenticator.py:292
+#: auths/SimpleLDAP/Authenticator.py:269 auths/SimpleLDAP/Authenticator.py:313
+msgid "Username not found"
+msgstr "Benutzername wurde nicht gefunden"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:332
+#: auths/SimpleLDAP/Authenticator.py:302
+msgid "Group not found"
+msgstr "Gruppe nicht gefunden"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:364
+#: auths/ActiveDirectory_enterprise/Authenticator.py:381
+#: auths/EDirectory_enterprise/Authenticator.py:303
+#: auths/RegexLdap/Authenticator.py:309 auths/SimpleLDAP/Authenticator.py:328
+#: auths/SimpleLDAP/Authenticator.py:342
+msgid "Too many results, be more specific"
+msgstr "Zu viele Ergebnisse, sein mehr spezifisch"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:404
+msgid "Domain seems to be incorrect, please check it"
+msgstr "Domäne scheint falsch sein, bitte überprüfen es"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:409
+msgid "Ldap does not seem an Active Directory (do not have user objects)"
+msgstr "LDAP scheint nicht Active Directory (keine Benutzerobjekte)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:417
+msgid "Ldap does not seem an Active Directory (no not have group objects)"
+msgstr "LDAP scheint nicht Active Directory (Nein, nicht haben Gruppenobjekte)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:425
+msgid ""
+"Ldap does not seem an Active Directory (do not have any user nor groups)"
+msgstr ""
+"LDAP scheint nicht Active Directory (nicht haben keine Benutzer oder Gruppen)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:430
+#: auths/EDirectory_enterprise/Authenticator.py:358
+#: auths/RegexLdap/Authenticator.py:384 auths/SimpleLDAP/Authenticator.py:423
+msgid "Connection params seem correct, test was succesfully executed"
+msgstr "Verbindung Params scheinen korrekt, Test wurde erfolgreich ausgeführt"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "Port"
+msgstr "Port"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+msgid "Ldap port (389 for non ssl, 636 for ssl normally"
+msgstr "LDAP-Port (389 für nicht Ssl, 636 für Ssl normalerweise"
+
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid ""
+"If checked, will use a ssl connection to ldap (if port is 389, will use in "
+"fact port 636)"
+msgstr ""
+"Wenn diese Option aktiviert ist, verwendet eine Ssl-Verbindung zum Ldap "
+"(Wenn Port 389 ist, wird in Tatsache Port 636)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+#, fuzzy
+msgid "Admin user"
+msgstr "Admin"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+#, fuzzy
+msgid "Username with read privileges on the eDirectory"
+msgstr "Benutzernamen mit lesen Berechtigungen auf der Basis ausgewählt"
+
+#: auths/EDirectory_enterprise/Authenticator.py:67
+#, fuzzy
+msgid "eDirectory Authenticator"
+msgstr "Active Directory-Authenticator"
+
+#: auths/EDirectory_enterprise/Authenticator.py:69
+#, fuzzy
+msgid "Authenticate against eDirectory"
+msgstr "Authentifikator mit Active Directory"
+
+#: auths/EDirectory_enterprise/Authenticator.py:323
+#: auths/RegexLdap/Authenticator.py:329 auths/SimpleLDAP/Authenticator.py:363
+msgid "Ldap search base is incorrect"
+msgstr "LDAP-Suche base ist falsch"
+
+#: auths/EDirectory_enterprise/Authenticator.py:328
+#: auths/RegexLdap/Authenticator.py:334 auths/SimpleLDAP/Authenticator.py:368
+msgid "Ldap user class seems to be incorrect (no user found by that class)"
+msgstr ""
+"LDAP-Benutzerklasse scheint nicht korrekt (kein Benutzer gefunden durch "
+"diese Klasse)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:336
+#: auths/RegexLdap/Authenticator.py:350 auths/SimpleLDAP/Authenticator.py:384
+msgid ""
+"Ldap user id attribute seems to be incorrect (no user found by that "
+"attribute)"
+msgstr ""
+"LDAP-Benutzer-Id-Attribut scheint falsch (kein Benutzer gefunden damit -"
+"Attribut)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:344
+msgid "Expected group attribute "
+msgstr "Erwartete Group-Attribut "
+
+#: auths/EDirectory_enterprise/Authenticator.py:353
+#, fuzzy
+msgid ""
+"Ldap user class or user id attr is probably wrong (Ldap is an eDirectory?)"
+msgstr ""
+"LDAP Benutzer Klasse oder Benutzer-Id Attr ist wahrscheinlich falsch (keiner "
+"Benutzer mit finden beide Bedingungen)"
+
+#: auths/IP/Authenticator.py:45 auths/IP/Authenticator.py:47
+msgid "IP Authenticator"
+msgstr "IP-Authenticator"
+
+#: auths/IP/Authenticator.py:51
+msgid "IP"
+msgstr "IP "
+
+#: auths/IP/Authenticator.py:52
+msgid "IP Range"
+msgstr "IP-Bereich"
+
+#: auths/IP/Authenticator.py:99 auths/InternalDB/Authenticator.py:102
+msgid "All seems fine in the authenticator."
+msgstr "Alles scheint in Ordnung in der Authentifikator."
+
+#: auths/InternalDB/Authenticator.py:46
+msgid "Internal Database"
+msgstr "Interne Datenbank"
+
+#: auths/InternalDB/Authenticator.py:48
+msgid "Internal dabasase authenticator. Doesn't uses external sources"
+msgstr "Interne Dabasase Authentifikator. Keine verwendet externe Quellen"
+
+#: auths/InternalDB/Authenticator.py:99
+msgid "Internal structures seems ok"
+msgstr "Interne Strukturen scheint in Ordnung"
+
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Username with read privileges on the base selected"
+msgstr "Benutzernamen mit lesen Berechtigungen auf der Basis ausgewählt"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Base"
+msgstr "Base"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Common search base (used for \"users\" and \"groups\""
+msgstr "Gemeinsame Suchbeginn (verwendet für \"Users\" und \"Gruppen\""
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "User class"
+msgstr "User-Klasse"
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "Class for LDAP users (normally posixAccount)"
+msgstr "Klasse für LDAP-Benutzer (normalerweise \"posixAccount\")"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "User Id Attr"
+msgstr "Benutzer-Id Attr"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "Attribute that contains the user id"
+msgstr "Attribut, das die Benutzer-Id enthält"
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "User Name Attr"
+msgstr "Benutzer Name Attr"
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "Attributes that contains the user name (list of comma separated values)"
+msgstr ""
+"Attribute, die dem Benutzernamen (Liste der durch Kommas getrennte Werte)"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Group Name Attr"
+msgstr "Gruppe Name Attr"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Attribute that contains the group name"
+msgstr "Attribut, das den Namen den Gruppe enthält"
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Exp. for groups"
+msgstr "Regelmäßige EXP für Gruppen."
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Expression to extract the group name"
+msgstr "Reguläre Ausdruck, der den Namen den Gruppe zu extrahieren"
+
+#: auths/RegexLdap/Authenticator.py:62
+msgid "Regex LDAP Authenticator"
+msgstr "Regex LDAP-Authenticator"
+
+#: auths/RegexLdap/Authenticator.py:64
+msgid "Regular Expressions LDAP authenticator"
+msgstr "Reguläre Ausdrücke LDAP-Authentifizierungsserver"
+
+#: auths/RegexLdap/Authenticator.py:96
+msgid "Invalid regular expression"
+msgstr ""
+
+#: auths/RegexLdap/Authenticator.py:342 auths/SimpleLDAP/Authenticator.py:376
+msgid "Ldap group class seems to be incorrect (no group found by that class)"
+msgstr ""
+"LDAP Gruppe Klasse scheint nicht korrekt (keine Gruppe gefunden durch diese "
+"Klasse)"
+
+#: auths/RegexLdap/Authenticator.py:360 auths/SimpleLDAP/Authenticator.py:392
+msgid ""
+"Ldap group id attribute seems to be incorrect (no group found by that "
+"attribute)"
+msgstr ""
+"LDAP Gruppe Id-Attribut scheint nicht korrekt (keine Gruppe gefunden, die -"
+"Attribut)"
+
+#: auths/RegexLdap/Authenticator.py:369 auths/SimpleLDAP/Authenticator.py:401
+msgid ""
+"Ldap user class or user id attr is probably wrong (can't find any user with "
+"both conditions)"
+msgstr ""
+"LDAP Benutzer Klasse oder Benutzer-Id Attr ist wahrscheinlich falsch (keiner "
+"Benutzer mit finden beide Bedingungen)"
+
+#: auths/SAML_enterprise/SAML.py:79
+#, fuzzy
+msgid "SAML Authenticator"
+msgstr "Authentifikator"
+
+#: auths/SAML_enterprise/SAML.py:91
+#, fuzzy
+msgid "SAML (v2.0) Authenticator"
+msgstr "Authentifikator"
+
+#: auths/SAML_enterprise/SAML.py:110 templates/uds/internal_page.html:28
+msgid "User"
+msgstr "Benutzer"
+
+#: auths/SAML_enterprise/SAML.py:119
+msgid "Private key"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:120
+msgid ""
+"Private key used for sign and encription, as generated in base 64 from "
+"openssl"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:121
+msgid "Certificate"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:122
+msgid "Server certificate (public), , as generated in base 64 from openssl"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:123
+msgid "IDP Metadata"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:124
+msgid ""
+"You can enter here the URL or the IDP metadata or the metadata itself (xml)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:126
+msgid "Entity ID"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:127
+msgid "ID of the SP. If left blank, this will be autogenerated from server URL"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:129
+#, fuzzy
+msgid "User name attrs"
+msgstr "Benutzer Name Attr"
+
+#: auths/SAML_enterprise/SAML.py:130
+#, fuzzy
+msgid "Fields from where to extract user name"
+msgstr "Reguläre Ausdruck, der den Namen den Gruppe zu extrahieren"
+
+#: auths/SAML_enterprise/SAML.py:132
+#, fuzzy
+msgid "Group name attrs"
+msgstr "Gruppe Name Attr"
+
+#: auths/SAML_enterprise/SAML.py:133
+#, fuzzy
+msgid "Fields from where to extract the groups"
+msgstr "Reguläre Ausdruck, der den Namen den Gruppe zu extrahieren"
+
+#: auths/SAML_enterprise/SAML.py:135
+#, fuzzy
+msgid "Real name attrs"
+msgstr "Benutzer Name Attr"
+
+#: auths/SAML_enterprise/SAML.py:136
+#, fuzzy
+msgid "Fields from where to extract the real name"
+msgstr "Reguläre Ausdruck, der den Namen den Gruppe zu extrahieren"
+
+#: auths/SAML_enterprise/SAML.py:159
+msgid ""
+"Server certificate should be a valid PEM (PEM certificates starts with -----"
+"BEGIN CERTIFICATE-----)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:164
+msgid "Invalid server certificate. "
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:167
+msgid ""
+"Private key should be a valid PEM (PEM private keys starts with -----BEGIN "
+"RSA PRIVATE KEY-----"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:196
+msgid "Can't fetch url {0}: {1}"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:207
+msgid " (obtained from URL)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:208
+msgid "XML do not seems valid for IDP Metadata "
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:224
+msgid "Can't access idp metadata"
+msgstr ""
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Group class"
+msgstr "Group-Klasse"
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Class for LDAP groups (normally poxisGroup)"
+msgstr "Klasse für LDAP-Gruppen (normalerweise PoxisGroup)"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Group Id Attr"
+msgstr "Gruppe Id Attr"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Attribute that contains the group id"
+msgstr "Attribut, das die Gruppen-Id enthält"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Group membership attr"
+msgstr "Gruppe Mitgliedschaft attr"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Attribute of the group that contains the users belonging to it"
+msgstr "Attribut der Gruppe mit die Benutzern gehören es"
+
+#: auths/SimpleLDAP/Authenticator.py:63
+msgid "SimpleLDAP Authenticator"
+msgstr "SimpleLDAP Authenticator"
+
+#: auths/SimpleLDAP/Authenticator.py:65
+msgid "Simple LDAP authenticator"
+msgstr "Einfache LDAP-Authentifizierungsserver"
+
+#: auths/SimpleLDAP/Authenticator.py:410
+msgid ""
+"Ldap group class or group id attr is probably wrong (can't find any group "
+"with both conditions)"
+msgstr ""
+"LDAP Gruppe Klasse oder Gruppe Id Attr ist wahrscheinlich falsch (kann nicht "
+"finden jede Gruppe beide Bedingungen)"
+
+#: auths/SimpleLDAP/Authenticator.py:417
+msgid "Can't locate any group with the membership attribute specified"
+msgstr ""
+"Jede Gruppe kann nicht mit das angegebene Mitgliedschaft-Attribut gefunden "
+"werden"
+
+#: core/BaseModule.py:196
+msgid "No connection checking method is implemented."
+msgstr "Keine Verbindung überprüfen Methode ist implementiert."
+
+#: core/BaseModule.py:253
+msgid "No check method provided."
+msgstr "Keine Prüfmethode zur Verfügung gestellt."
+
+#: core/managers/PublicationManager.py:152
+msgid ""
+"Already publishing. Wait for previous publication to finish and try again"
+msgstr ""
+"Bereits veröffentlicht. Warten Sie auf vorherige Veröffentlichung zu beenden "
+"und versuchen Sie es erneut"
+
+#: core/managers/PublicationManager.py:165
+msgid "Can't cancel non running publication"
+msgstr "Nicht ausgeführte Veröffentlichung kann nicht abgebrochen werden."
+
+#: core/managers/PublicationManager.py:183
+msgid "Can't unpublish non usable publication"
+msgstr "Kann nicht nicht nutzbare Veröffentlichung aufheben"
+
+#: core/managers/PublicationManager.py:186
+msgid "Can't unpublish publications with services in process"
+msgstr ""
+"Publikationen mit Dienstleistungen im Prozess kann nicht Veröffentlichung "
+"rückgängig machen"
+
+#: core/managers/UserPrefsManager.py:254
+msgid "Screen Size"
+msgstr "Bildschirmgröße"
+
+#: core/managers/UserPrefsManager.py:258
+msgid "Full Screen"
+msgstr "Vollbild"
+
+#: core/managers/UserPrefsManager.py:261
+msgid "Screen colors"
+msgstr "Bildschirmfarben"
+
+#: core/managers/UserPrefsManager.py:262
+msgid "8 bits"
+msgstr "8 Bit"
+
+#: core/managers/UserPrefsManager.py:263
+msgid "16 bits"
+msgstr "16 bits"
+
+#: core/managers/UserPrefsManager.py:264
+msgid "24 bits"
+msgstr "24 Bit"
+
+#: core/managers/UserPrefsManager.py:265
+msgid "32 bits"
+msgstr "32 bits"
+
+#: core/managers/UserServiceManager.py:303
+msgid "Can't cancel non running operation"
+msgstr "Kann nicht nicht ausgeführten Vorgang abbrechen"
+
+#: core/managers/UserServiceManager.py:322
+msgid "Can't remove a non active element"
+msgstr "Ein nicht aktive Element kann nicht entfernt werden."
+
+#: core/managers/UserServiceManager.py:335
+msgid "Can't remove nor cancel {0} cause its states doesn't allows it"
+msgstr ""
+"Kann weder entfernen noch Abbrechen {0} Ursache ihrer Staaten nicht erlaubt "
+"es"
+
+#: core/osmanagers/BaseOsManager.py:49
+msgid "Base OS Manager"
+msgstr "Base OS Manager"
+
+#: core/osmanagers/BaseOsManager.py:51
+msgid "Base Manager"
+msgstr "Base Manager"
+
+#: core/transports/BaseTransport.py:113
+msgid "Transport empty"
+msgstr "Transport leer"
+
+#: core/util/State.py:59
+msgid "Active"
+msgstr "Aktive"
+
+#: core/util/State.py:59
+msgid "Inactive"
+msgstr "Inaktiv"
+
+#: core/util/State.py:59
+msgid "Blocked"
+msgstr "Blockiert"
+
+#: core/util/State.py:59
+msgid "Waiting publication"
+msgstr "Wartet auf Veröffentlichung"
+
+#: core/util/State.py:60
+msgid "In preparation"
+msgstr "In Vorbereitung"
+
+#: core/util/State.py:60
+msgid "Valid"
+msgstr "Gültig"
+
+#: core/util/State.py:61
+msgid "Waiting for removal"
+msgstr "Warten auf Entfernung"
+
+#: core/util/State.py:61
+msgid "Removing"
+msgstr "Entfernen"
+
+#: core/util/State.py:61
+msgid "Removed"
+msgstr "Entfernt"
+
+#: core/util/State.py:61
+msgid "Canceled"
+msgstr "Abgebrochen"
+
+#: core/util/State.py:62
+msgid "Canceling"
+msgstr "Abbrechen"
+
+#: core/util/State.py:62 templates/uds/error.html:6
+#: templates/uds/error.html.py:11
+msgid "Error"
+msgstr "Fehler"
+
+#: core/util/State.py:62
+msgid "Running"
+msgstr "Ausführen"
+
+#: core/util/State.py:62
+msgid "Finished"
+msgstr "Fertig"
+
+#: core/util/State.py:62
+msgid "Waiting execution"
+msgstr "Personen, die auf Ausführung"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:45
+msgid "Linux OS Manager"
+msgstr "Linux OS Manager"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:47
+msgid ""
+"Os Manager to control linux virtual machines (basically renames machine and "
+"notify state)"
+msgstr ""
+"OS-Manager, um die Kontrolle von Linux virtuellen Maschinen (im Grunde "
+"benennt Maschine und Benachrichtigen Sie Zustand)"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "On Logout"
+msgstr "Beim Abmelden"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "What to do when user logout from service"
+msgstr "Was tun, wenn Benutzer abmelden vom Dienst"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:51
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:42
+msgid "Keep service assigned"
+msgstr "Halten Sie Dienst zugewiesen"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:52
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:43
+msgid "Remove service"
+msgstr "Basisdienst"
+
+#: osmanagers/LinuxOsManager/__init__.py:43
+msgid "UDS Actor for linux machines (Requires python 2.6 or greater)"
+msgstr ""
+"UDS Schauspieler für Linux Maschinen (Python 2.6 oder höher erforderlich)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:23
+msgid "Windows Domain OS Manager"
+msgstr "Windows OS Gebietsmanager"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:25
+msgid ""
+"Os Manager to control windows machines with domain. (Basically renames "
+"machine)"
+msgstr ""
+"OS-Manager, um Windows-Maschinen mit Domäne steuern. (Im Grunde benennt "
+"Maschine)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid "Domain"
+msgstr "Domäne"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+msgid "Domain to join machines to (better use dns form of domain)"
+msgstr "Domäne beitreten Maschinen (bessere DNS-Form der Domäne verwenden)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "Account"
+msgstr "Konto"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+msgid "Account with rights to add machines to domain"
+msgstr "Konto mit der Berechtigung zum Computer zur Domäne hinzufügen"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+msgid "Password of the account"
+msgstr "Kennwort für das Konto"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid "OU"
+msgstr "OU"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid ""
+"Organizational unit where to add machines in domain (check it before using "
+"it)"
+msgstr ""
+"Organisationseinheit, an Maschinen in Domäne hinzugefügt (überprüfen sie vor "
+"der Verwendung es)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:40
+msgid "Must provide a domain!!!"
+msgstr "Müssen eine Domäne zur Verfügung stellen!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:42
+msgid "Must provide an account to add machines to domain!!!"
+msgstr "Muss ein Konto Domäne Maschinen hinzu bieten!!!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:44
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:41
+msgid "Must provide a password for the account!!!"
+msgstr "Muss ein Kennwort für das Konto angeben!"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:23
+#, fuzzy
+msgid "Windows Random Password OS Manager"
+msgstr "Windows OS Gebietsmanager"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:25
+#, fuzzy
+msgid ""
+"Os Manager to control windows machines, with user password set randomly."
+msgstr ""
+"OS-Manager, um Windows-Maschinen mit Domäne steuern. (Im Grunde benennt "
+"Maschine)"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "User account to change password"
+msgstr ""
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+#, fuzzy
+msgid "Current (template) password of the user account"
+msgstr "Muss ein Kennwort für das Konto angeben!"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:39
+#, fuzzy
+msgid "Must provide an user account!!!"
+msgstr "Muss ein Kennwort für das Konto angeben!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:36
+msgid "Windows Basic OS Manager"
+msgstr "Grundlegende Windows-OS-Manager"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:38
+msgid ""
+"Os Manager to control windows machines without domain. (Basically renames "
+"machine)"
+msgstr ""
+"OS-Manager, um Windows-Rechner ohne Domäne steuern. (Im Grunde benennt "
+"Maschine)"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:52
+msgid "Length must be numeric!!"
+msgstr "Länge muss numerisch sein!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:54
+msgid "Length must be betwen 1 and six"
+msgstr "Länge muss zwischen 1 und 6"
+
+#: osmanagers/WindowsOsManager/__init__.py:25
+msgid ""
+"UDS Actor for windows machines (Important!! Requires .net framework 3.5 "
+"sp1)"
+msgstr ""
+"UDS Schauspieler für Windows-Rechner (wichtig! .Net Framework 3.5 "
+"erfordert SP1)"
+
+#: services/OVirt/OVirtLinkedService.py:56
+#, fuzzy
+msgid "oVirt Linked Clone"
+msgstr "VMWare Linked Clone-base"
+
+#: services/OVirt/OVirtLinkedService.py:60
+msgid "oVirt Services based on templates and COW"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:77
+#: services/Vmware_enterprise/VCLinkedCloneService.py:70
+msgid "Number of desired machines to keep running waiting for a user"
+msgstr ""
+"Anzahl der gewünschten Maschinen warten für einen Benutzer ausgeführt wird"
+
+#: services/OVirt/OVirtLinkedService.py:83
+#: services/Vmware_enterprise/VCLinkedCloneService.py:72
+msgid "Number of desired machines to keep suspended waiting for use"
+msgstr ""
+"Anzahl der gewünschten Maschinen zu ausgesetzten Personen, die für die "
+"Verwendung auf"
+
+#: services/OVirt/OVirtLinkedService.py:99
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+#, fuzzy
+msgid "Base Machine"
+msgstr "Base Manager"
+
+#: services/OVirt/OVirtLinkedService.py:99
+#, fuzzy
+msgid "Service base machine"
+msgstr "Grundmaschine"
+
+#: services/OVirt/OVirtLinkedService.py:100
+msgid "Cluster"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:106
+msgid "Cluster to contain services"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:109
+#, fuzzy
+msgid "Datastore Domain"
+msgstr "Datastores"
+
+#: services/OVirt/OVirtLinkedService.py:110
+#, fuzzy
+msgid "Datastore domain where to publish and put incrementals"
+msgstr "Datastores wo inkrementelle Backups"
+
+#: services/OVirt/OVirtLinkedService.py:112
+#: services/Vmware_enterprise/VCLinkedCloneService.py:49
+msgid "Memory (Mb)"
+msgstr "Speicher (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:113
+msgid "Memory assigned to machines"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:115
+#, fuzzy
+msgid "Memory Guaranteed (Mb)"
+msgstr "Speicher (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:116
+msgid "Physical memory guaranteed to machines"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:118
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Machine Names"
+msgstr "Computernamen"
+
+#: services/OVirt/OVirtLinkedService.py:119
+#: services/Vmware_enterprise/VCLinkedCloneService.py:54
+msgid "Name Length"
+msgstr "Länge des Dateinamens"
+
+#: services/OVirt/OVirtLinkedService.py:120
+#: services/Vmware_enterprise/VCLinkedCloneService.py:55
+msgid "Length of numeric part for the names of this machines (betwen 3 and 6"
+msgstr ""
+"Länge der numerische Teil für die Namen dieser Maschinen (zwischen 3 und 6"
+
+#: services/OVirt/OVirtLinkedService.py:122
+msgid "Display"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:123
+msgid "Display type (only for administration pourposses)"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:143
+#: services/Vmware_enterprise/VCLinkedCloneService.py:93
+msgid "The length of basename plus length must not be greater than 15"
+msgstr "Die Länge der Basename plus Länge darf nicht größer als 15 sein."
+
+#: services/OVirt/OVirtLinkedService.py:145
+#: services/Vmware_enterprise/VCLinkedCloneService.py:95
+msgid "The machine name can't be only numbers"
+msgstr "Der Computername kann nicht nur Zahlen sein."
+
+#: services/OVirt/OVirtProvider.py:72
+#, fuzzy
+msgid "oVirt Platform Provider"
+msgstr "VMWare Virtual Center-Provider"
+
+#: services/OVirt/OVirtProvider.py:76
+#, fuzzy
+msgid "oVirt platform service provider"
+msgstr "Der Diensteanbieter kann nicht gefunden werden."
+
+#: services/OVirt/OVirtProvider.py:91
+#, fuzzy
+msgid "oVirt Server IP or Hostname"
+msgstr "VMWare VC Server IP oder Hostname"
+
+#: services/OVirt/OVirtProvider.py:92
+#, fuzzy
+msgid "User with valid privileges on oVirt, (use \"user@domain\" form)"
+msgstr "Benutzer mit gültigen Berechtigungen für VC"
+
+#: services/OVirt/OVirtProvider.py:93
+#, fuzzy
+msgid "Password of the user of oVirt"
+msgstr "Kennwort des Benutzers des VC"
+
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout in seconds of connection to VC"
+msgstr "Timeout in Sekunden der Verbindung zum VC"
+
+#: services/OVirt/OVirtProvider.py:95
+#: services/Vmware_enterprise/ServiceProviderVC.py:33
+msgid "Macs range"
+msgstr "Mac-Bereich"
+
+#: services/OVirt/OVirtProvider.py:96
+#: services/Vmware_enterprise/ServiceProviderVC.py:34
+msgid "Range of valids macs for created machines"
+msgstr "Bereich von gilt Macs für erstellte Maschinen"
+
+#: services/OVirt/OVirtProvider.py:395
+msgid "Connection test successful"
+msgstr ""
+
+#: services/OVirt/OVirtProvider.py:396
+#: services/Vmware_enterprise/ServiceProviderVC.py:120
+msgid "Connection failed. Check connection params"
+msgstr "Verbindung ist fehlgeschlagen. Kontrollkästchen Verbindung params"
+
+#: services/OVirt/OVirtPublication.py:85
+#, fuzzy
+msgid "UDS pub for {0} at {1}"
+msgstr "UDS-Veröffentlichung erstellt am {1} {0}"
+
+#: services/PhysicalMachines/IPMachineDeployed.py:57
+msgid "IP "
+msgstr "IP "
+
+#: services/PhysicalMachines/IPMachinesService.py:46
+msgid "List of IPS"
+msgstr "Liste der IPS"
+
+#: services/PhysicalMachines/IPMachinesService.py:49
+msgid "Physical machines accesed by ip"
+msgstr "Physische Computer Zugang von ip"
+
+#: services/PhysicalMachines/IPMachinesService.py:51
+msgid "This service provides access to POWERED-ON Machines by ip"
+msgstr ""
+"Dieser Service ermöglicht den Zugriff auf verknüpfte Clones Maschinen auf "
+"einen Virtual Center"
+
+#: services/Sample/SampleProvider.py:138
+msgid "Methuselah is not alive!!! :-)"
+msgstr "Methusalem ist nicht lebendig!!! :-)"
+
+#: services/Sample/SampleProvider.py:178
+msgid "Nothing tested, but all went fine.."
+msgstr "Nichts getestet, aber alles ging gut..."
+
+#: services/Sample/SamplePublication.py:195
+msgid "Random integer was 9!!! :-)"
+msgstr "Zufällige ganze war 9!!! :-)"
+
+#: services/Vmware_enterprise/Helpers.py:72
+msgid "Local"
+msgstr "Lokale"
+
+#: services/Vmware_enterprise/Helpers.py:74
+msgid "Remote"
+msgstr "Entfernte"
+
+#: services/Vmware_enterprise/PublicationVC.py:37
+msgid "Publication"
+msgstr "Veröffentlichung"
+
+#: services/Vmware_enterprise/PublicationVC.py:38
+#, fuzzy
+msgid "UDS Publication for {0} created at {1}"
+msgstr "UDS-Veröffentlichung erstellt am {1} {0}"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "VMWare VC Server Port (usually 443)"
+msgstr "VMWare VC Server Port (in der Regel 443)"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+#, fuzzy
+msgid "User with valid privileges on VC"
+msgstr "Benutzernamen mit lesen Berechtigungen auf der Basis ausgewählt"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+#, fuzzy
+msgid "Password of the user of the VC"
+msgstr "Kennwort des Benutzers des VC"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:39
+#, fuzzy
+msgid "VMWare Virtual Center Provider"
+msgstr "VmwareVC Provider: "
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:41
+msgid "Provides connection to Virtual Center Services"
+msgstr "Stellt Verbindung zu Virtual Center Services"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:110
+msgid "Error testing connection"
+msgstr "Fehler testen Verbindung"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:113
+msgid "VmwareVC Provider: "
+msgstr "VmwareVC Provider: "
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:119
+msgid "Connection params ok"
+msgstr "Verbindung Params ok"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:28
+msgid "Datacenter"
+msgstr "Datacenter"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:34
+msgid "Datacenter containing base machine"
+msgstr "Datacenter mit Basismaschine"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:36
+msgid "Network"
+msgstr "Netzwerk"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:37
+msgid ""
+"If more than 1 interface is found in machine, use one on this network as main"
+msgstr ""
+"Wenn mehr als 1 Schnittstelle in Maschine gefunden wird, verwenden Sie eine "
+"in diesem Netzwerk als wichtigsten"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Pub. Resource Pool"
+msgstr "Kneipe. Ressourcenpool"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Resource Pool where deploy clones"
+msgstr "Ressourcen-Pool in der Klone bereitgestellt"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Clones Folder"
+msgstr "Klone Ordner"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Folder where deploy clones"
+msgstr "Ordner wo bereitstellen Klone"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:40
+msgid "Resource Pool"
+msgstr "Ressourcenpool"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:46
+msgid "Resource Pool containing base machine"
+msgstr "Ressource Pool mit Basismaschine"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+msgid "Base machine for this service"
+msgstr "Grundmaschine für diesen Dienst"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:50
+msgid "Memory for machines deployed from this service"
+msgstr "Speicher für Maschinen, die von diesem Dienst bereitgestellt"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:51
+#, fuzzy
+msgid "Datastores"
+msgstr "Datastores"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:52
+#, fuzzy
+msgid "Datastores where to put incrementals"
+msgstr "Datastores wo inkrementelle Backups"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Base name for clones from this machine"
+msgstr "Basisname für Clones von diesem Computer"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:62
+#, fuzzy
+msgid "VMWare Linked clone base"
+msgstr "VMWare Linked Clone-base"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:64
+msgid ""
+"This service provides access to Linked Clones machines on a Virtual Center"
+msgstr ""
+"Dieser Service ermöglicht den Zugriff auf verknüpfte Clones Maschinen auf "
+"einen Virtual Center"
+
+#: templates/404.html:4 templates/404.html.py:7
+msgid "Page not found"
+msgstr "Seite nicht gefunden"
+
+#: templates/404.html:9
+msgid "Sorry, but the requested page could not be found."
+msgstr ""
+"Tut mir leid, aber die angeforderte Seite konnte nicht gefunden werden."
+
+#: templates/uds/base.html:7
+msgid "UDS"
+msgstr "UDS"
+
+#: templates/uds/detectJava.html:6
+msgid "Login redirection"
+msgstr ""
+
+#: templates/uds/detectJava.html:38
+msgid "Go to main page"
+msgstr ""
+
+#: templates/uds/downloads.html:8 templates/uds/snippets/admin_user.html:7
+msgid "Downloads"
+msgstr "Downloads"
+
+#: templates/uds/downloads.html:11
+msgid ""
+"This page contains a list of downloadables provided by different modules"
+msgstr ""
+"Diese Seite enthält eine Liste der Downloads von verschiedenen Modulen "
+"bereitgestellt"
+
+#: templates/uds/index.html:51
+msgid "Services"
+msgstr "Dienstleistungen"
+
+#: templates/uds/index.html:70
+msgid "Java not found"
+msgstr "Java nicht gefunden"
+
+#: templates/uds/index.html:71
+msgid ""
+"Java is not available on your browser, and the selected transport needs it."
+msgstr ""
+"Java ist nicht verfügbar in Ihrem Browser, und der ausgewählte Transport "
+"muss es."
+
+#: templates/uds/index.html:72
+msgid "Please, install latest version from"
+msgstr "Bitte installieren Sie neueste Version von"
+
+#: templates/uds/index.html:72
+msgid "Java website"
+msgstr "Java-website"
+
+#: templates/uds/index.html:72
+msgid "and restart browser"
+msgstr "und Browser neu starten"
+
+#: templates/uds/index.html:78
+msgid "Ip"
+msgstr "IP"
+
+#: templates/uds/index.html:79
+msgid "Networks"
+msgstr "Netzwerke"
+
+#: templates/uds/index.html:80
+msgid "Transports"
+msgstr "Transporte"
+
+#: templates/uds/internal_page.html:34 templates/uds/prefs.html:12
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: templates/uds/internal_page.html:40
+msgid "Log out"
+msgstr "Melden Sie sich ab"
+
+#: templates/uds/login.html:6
+msgid "Login to UDS"
+msgstr "Anmeldung für UDS"
+
+#: templates/uds/login.html:78
+msgid "Login"
+msgstr "Anmeldung"
+
+#: templates/uds/login.html:83
+msgid "Login data"
+msgstr "Login-Daten"
+
+#: templates/uds/login.html:86
+msgid "Enter"
+msgstr "Geben Sie"
+
+#: templates/uds/login.html:93
+msgid "Back to login"
+msgstr "Zurück zur Anmeldung"
+
+#: templates/uds/prefs.html:6
+msgid "UDS User Preferences"
+msgstr "UDS-Benutzereinstellungen"
+
+#: templates/uds/prefs.html:16
+msgid "Save Preferences"
+msgstr "Speichern Sie Einstellungen"
+
+#: templates/uds/service_not_ready.html:6
+msgid "Service not ready at this moment. Please, try again in a while."
+msgstr ""
+"Service im Moment nicht bereit. Bitte, versuchen Sie es noch einmal in eine "
+"Weile."
+
+#: templates/uds/snippets/admin_user.html:4
+msgid "Admin"
+msgstr "Admin"
+
+#: templates/uds/snippets/back_to_list.html:3
+msgid "Back to services list"
+msgstr "Zurück zur Liste"
+
+#: templates/uds/snippets/lang.html:9
+msgid "Language"
+msgstr "Sprache"
+
+#: transports/NX/NXTransport.py:54
+msgid "NX Transport (direct)"
+msgstr "NX Transport (direkt)"
+
+#: transports/NX/NXTransport.py:56
+msgid "NX Transport for direct connection"
+msgstr "NX-Transport für den direkten Anschluss"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "Empty creds"
+msgstr "Leere creds"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "If checked, the credentials used to connect will be emtpy"
+msgstr ""
+"Wenn diese Option aktiviert, werden die Anmeldeinformationen zum Herstellen "
+"einer leer sein."
+
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66
+msgid "If not empty, this username will be always used as credential"
+msgstr ""
+"Wenn nicht leer ist, dieser Benutzername wird immer als verwendet "
+"Anmeldeinformationen"
+
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67
+msgid "If not empty, this password will be always used as credential"
+msgstr ""
+"Wenn nicht leer ist, dieses Kennwort immer verwendet werden als "
+"Anmeldeinformationen"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listen port"
+msgstr "-Listenanschluss"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listening port of NX (ssh) at client machine"
+msgstr "Hören Port des NX (ssh) bei Client-Rechner"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection"
+msgstr "Verbindung"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection speed for this transport (quality)"
+msgstr "Verbindungsgeschwindigkeit für diesen Transport (Qualität)"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Session"
+msgstr "Sitzung"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Desktop session"
+msgstr "Desktop-Sitzung"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Disk Cache"
+msgstr "Festplatten-Cache"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Cache size en Mb stored at disk"
+msgstr "Cache-Größe de Mb auf der Festplatte gespeichert"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Memory Cache"
+msgstr "Memory-Caches"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Cache size en Mb keept at memory"
+msgstr "Cache Größe de Mb Keept auf Speicher"
+
+#: transports/NX/__init__.py:40 transports/TSNX/__init__.py:40
+msgid "NX Protocol"
+msgstr "NX-Protokoll"
+
+#: transports/NX/__init__.py:46
+msgid "UDS Actor connector for NX (requires nomachine packages)"
+msgstr "UDS Schauspieler Connector für NX (erfordert Nomachine Pakete)"
+
+#: transports/NX/web.py:74
+msgid ""
+"In order to use this transport, you need to install first Nomachine Nx "
+"Client version 3.5.x"
+msgstr ""
+"Um diesen Transport verwenden, müssen Sie installieren ersten Nomachine Nx "
+"Client Version 3.5.x"
+
+#: transports/NX/web.py:75
+msgid "you can obtain it for your platform from"
+msgstr "Sie können es für Ihre Plattform von abrufen"
+
+#: transports/NX/web.py:75
+msgid "nochamine web site"
+msgstr "Nochamine-Website"
+
+#: transports/RDP/RDPTransport.py:30
+msgid "RDP Transport (direct)"
+msgstr "RDP Transport (direkt)"
+
+#: transports/RDP/RDPTransport.py:32
+msgid "RDP Transport for direct connection"
+msgstr "RDP-Transport für den direkten Anschluss"
+
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid ""
+"If not empty, this domain will be always used as credential (used as DOMAIN"
+"\\user)"
+msgstr ""
+"Wenn nicht leer ist, dieser Domäne immer verwendet werden als "
+"Anmeldeinformationen (als DOMAIN verwendet\\Benutzer)"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "Allow Smartcards"
+msgstr "Ermöglichen Smartcards"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "If checked, this transport will allow the use of smartcards"
+msgstr ""
+"Wenn aktiviert, wird dieser Transport ermöglichen die Verwendung von "
+"smartcards"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "Allow Printers"
+msgstr "Druckerveröffentlichung zulassen"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "If checked, this transport will allow the use of user printers"
+msgstr ""
+"Wenn diese Option aktiviert, wird dieser Transport die Verwendung von "
+"Benutzer Drucker ermöglichen."
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "Allow Drives"
+msgstr "Laufwerke ermöglichen"
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "If checked, this transport will allow the use of user drives"
+msgstr ""
+"Wenn aktiviert, wird dieser Transport die Verwendung von Benutzer-Laufwerke "
+"erlauben."
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "Allow Serials"
+msgstr "Ermöglichen Serien"
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "If checked, this transport will allow the use of user serial ports"
+msgstr ""
+"Wenn aktiviert, wird dieser Transport die Verwendung des Benutzers erlauben "
+"serielle ports"
+
+#: transports/RDP/TSRDPTransport.py:31
+msgid "RDP Transport (tunneled)"
+msgstr "RDP-Verkehr (Tunneling)"
+
+#: transports/RDP/TSRDPTransport.py:33
+msgid "RDP Transport for tunneled connection"
+msgstr "RDP-Verkehr für getunnelte Verbindung"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid "Tunnel server"
+msgstr "Tunnel-server"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid ""
+"IP or Hostname of tunnel server send to client device (\"public\" ip) and "
+"port. (use HOST:PORT format)"
+msgstr ""
+"IP-Adresse oder Hostname des Tunnel-Server senden an Client-Gerät "
+"(\"öffentliche\" IP-Adresse) und Port. (verwenden Sie HOST: PORT-Format)"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid "Tunnel host check"
+msgstr "Tunnel Host-Prüfung"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid ""
+"If not empty, this server will be used to check if service is running before "
+"assigning it to user. (use HOST:PORT format)"
+msgstr ""
+"Wenn nicht leer ist, wird dieser Server zu überprüfen, ob der Dienst "
+"ausgeführt wird, bevor Sie verwendet werden Benutzer zuweisen. (verwenden "
+"Sie HOST: PORT-Format)"
+
+#: transports/RDP/__init__.py:17
+msgid "Remote Desktop Protocol"
+msgstr "Remote Desktop-Protokoll"
+
+#: transports/RDP/web.py:83
+msgid "In order to use this service, you should first install CoRD."
+msgstr "Um diesen Dienst zu nutzen, sollten Sie zunächst Kabel installieren."
+
+#: transports/RDP/web.py:84 transports/RGS-enterprise/web.py:82
+msgid "You can obtain it from"
+msgstr "Erhalten Sie es aus"
+
+#: transports/RDP/web.py:84
+msgid "CoRD Website"
+msgstr "CoRD-Website"
+
+#: transports/RGS-enterprise/RGSTransport.py:34
+#, fuzzy
+msgid "RGS Transport (direct)"
+msgstr "RDP Transport (direkt)"
+
+#: transports/RGS-enterprise/RGSTransport.py:36
+#, fuzzy
+msgid "RGS Transport for direct connection"
+msgstr "RDP-Transport für den direkten Anschluss"
+
+#: transports/RGS-enterprise/RGSTransport.py:45
+#: transports/RGS-enterprise/TRGSTransport.py:50
+msgid "Image quality"
+msgstr "Bildqualität"
+
+#: transports/RGS-enterprise/RGSTransport.py:46
+#: transports/RGS-enterprise/TRGSTransport.py:51
+msgid "Quality of image codec (0-100)"
+msgstr "Qualität der Image-Codec (0-100)"
+
+#: transports/RGS-enterprise/RGSTransport.py:47
+#: transports/RGS-enterprise/TRGSTransport.py:52
+msgid "Adjustable Quality"
+msgstr "Einstellbare Qualität"
+
+#: transports/RGS-enterprise/RGSTransport.py:48
+#: transports/RGS-enterprise/TRGSTransport.py:53
+msgid "If checked, the image quality will be adjustable with bandwidth"
+msgstr ""
+"Wenn diese Option aktiviert, wird die Bildqualität mit Bandbreite "
+"einstellbar sein"
+
+#: transports/RGS-enterprise/RGSTransport.py:49
+#: transports/RGS-enterprise/TRGSTransport.py:54
+msgid "Min. Adjustable Quality"
+msgstr "Min. einstellbare Qualität"
+
+#: transports/RGS-enterprise/RGSTransport.py:50
+#: transports/RGS-enterprise/TRGSTransport.py:55
+msgid ""
+"The lowest image quality applied to images to maintain the minimum update "
+"rate."
+msgstr ""
+"Die niedrigsten Bildqualität auf Bilder beibehalten das minimale Update "
+"angewendet Preise."
+
+#: transports/RGS-enterprise/RGSTransport.py:51
+#: transports/RGS-enterprise/TRGSTransport.py:56
+msgid "Adjustable Frame Rate"
+msgstr "Einstellbare Framerate"
+
+#: transports/RGS-enterprise/RGSTransport.py:52
+#: transports/RGS-enterprise/TRGSTransport.py:57
+msgid "Update rate threshold to begin adjusting image quality"
+msgstr "Update-Rate Schwellenwert beginnen, Anpassen der Bildqualität"
+
+#: transports/RGS-enterprise/RGSTransport.py:53
+#: transports/RGS-enterprise/TRGSTransport.py:58
+msgid "Match Local Resolution"
+msgstr "Spiel Ortsauflösung"
+
+#: transports/RGS-enterprise/RGSTransport.py:54
+#: transports/RGS-enterprise/TRGSTransport.py:59
+msgid ""
+"Change the Sender's resolution to match the Receiver's resolution when "
+"connecting"
+msgstr ""
+"Ändern des Absenders Auflösung Auflösung des Empfängers übereinstimmen wenn "
+"verbinden"
+
+#: transports/RGS-enterprise/RGSTransport.py:55
+#: transports/RGS-enterprise/TRGSTransport.py:60
+msgid "Redirect USB"
+msgstr "Umleitung USB"
+
+#: transports/RGS-enterprise/RGSTransport.py:56
+#: transports/RGS-enterprise/TRGSTransport.py:61
+msgid "If checked, the USB will be redirected."
+msgstr "Wenn diese Option aktiviert, wird die USB umgeleitet werden."
+
+#: transports/RGS-enterprise/RGSTransport.py:57
+#: transports/RGS-enterprise/TRGSTransport.py:62
+msgid "Redirect Audio"
+msgstr "Audio umleiten"
+
+#: transports/RGS-enterprise/RGSTransport.py:58
+#: transports/RGS-enterprise/TRGSTransport.py:63
+#, fuzzy
+msgid "If checked, the Audio will be redirected."
+msgstr ""
+"Wenn diese Option aktiviert, werden die Anmeldeinformationen zum Herstellen "
+"einer leer sein."
+
+#: transports/RGS-enterprise/RGSTransport.py:59
+#: transports/RGS-enterprise/TRGSTransport.py:64
+msgid "Redirect Mic"
+msgstr "Umleitung Mic"
+
+#: transports/RGS-enterprise/RGSTransport.py:60
+#: transports/RGS-enterprise/TRGSTransport.py:65
+#, fuzzy
+msgid "If checked, the Mic will be redirected."
+msgstr ""
+"Wenn diese Option aktiviert, werden die Anmeldeinformationen zum Herstellen "
+"einer leer sein."
+
+#: transports/RGS-enterprise/TRGSTransport.py:36
+#, fuzzy
+msgid "RGS Transport (tunneled)"
+msgstr "RDP-Verkehr (Tunneling)"
+
+#: transports/RGS-enterprise/TRGSTransport.py:38
+#, fuzzy
+msgid "RGS Transport for tunneled connection"
+msgstr "RDP-Verkehr für getunnelte Verbindung"
+
+#: transports/RGS-enterprise/web.py:81
+#, fuzzy
+msgid "In order to use this service, you should first install RGS Receiver."
+msgstr "Um diesen Dienst zu nutzen, sollten Sie zunächst Kabel installieren."
+
+#: transports/RGS-enterprise/web.py:82
+#, fuzzy
+msgid "HP Website"
+msgstr "CoRD-Website"
+
+#: transports/TSNX/TSNXTransport.py:55
+msgid "NX Transport (tunneled)"
+msgstr "NX-Transport (Tunneling)"
+
+#: transports/TSNX/TSNXTransport.py:57
+msgid "NX Transport for tunneled connection"
+msgstr "NX-Transport für getunnelte Verbindung"
+
+#: web/errors.py:58
+msgid "Unknown error"
+msgstr "Unbekannter Fehler"
+
+#: web/errors.py:59
+msgid "Transport not found"
+msgstr "Verkehr nicht gefunden"
+
+#: web/errors.py:60
+msgid "Service not found"
+msgstr "-Dienst nicht gefunden"
+
+#: web/errors.py:61 xmlrpc/auths/AdminAuth.py:171
+msgid "Access denied"
+msgstr "Zugriff verweigert"
+
+#: web/errors.py:62
+msgid ""
+"Invalid service. The service is not available at this moment. Please, try "
+"later"
+msgstr ""
+"Ungültiger Service. Der Dienst ist nicht verfügbar im Moment. Bitte, "
+"versuchen Sie später"
+
+#: web/errors.py:63
+msgid "Maximum services limit reached. Please, contact administrator"
+msgstr "Maximale Service Limit erreicht. Bitte kontaktieren Sie administrator"
+
+#: web/errors.py:64
+msgid "You need to enable cookies to let this application work"
+msgstr "Sie müssen Cookies, diese Anwendung arbeiten lassen aktivieren"
+
+#: web/errors.py:65
+msgid "User service not found"
+msgstr "Benutzer-Dienst nicht gefunden"
+
+#: web/errors.py:66
+#, fuzzy
+msgid "Authenticator not found"
+msgstr "Authentifikator"
+
+#: web/errors.py:67 xmlrpc/auths/AdminAuth.py:169
+msgid "Invalid authenticator"
+msgstr "Ungültige Echtheitsbestätigung"
+
+#: web/views.py:334
+#, fuzzy
+msgid "Authenticator do not provides information"
+msgstr "Authentifikator ist nicht vorhanden"
+
+#: web/forms/LoginForm.py:59
+msgid "Authenticator"
+msgstr "Authentifikator"
+
+#: xmlrpc/auths/AdminAuth.py:113
+msgid "Credentials no longer valid"
+msgstr "Anmeldeinformationen nicht mehr gültig"
+
+#: xmlrpc/auths/AdminAuth.py:149
+msgid "Administration"
+msgstr "Verwaltung"
+
+#: xmlrpc/auths/AdminAuth.py:164
+msgid "Invalid credentials"
+msgstr "Ungültiger Anmeldeinformationen"
+
+#: xmlrpc/auths/Authenticators.py:106
+msgid "Authenticator does not exists"
+msgstr "Authentifikator ist nicht vorhanden"
+
+#: xmlrpc/auths/Authenticators.py:161 xmlrpc/osmanagers/OSManagers.py:115
+#: xmlrpc/services/ServiceProviders.py:115
+#: xmlrpc/services/ServiceProviders.py:139 xmlrpc/services/Services.py:160
+#: xmlrpc/services/Services.py:184 xmlrpc/transports/Networks.py:86
+#: xmlrpc/transports/Networks.py:97
+#, python-format
+msgid "Name %s already exists"
+msgstr "Name %s existiert bereits"
+
+#: xmlrpc/auths/Authenticators.py:234
+msgid "Authenticator do not supports search"
+msgstr "Authentifikator ist nicht vorhanden"
+
+#: xmlrpc/auths/Authenticators.py:240
+msgid "Specified authenticator do not exists anymore. Please, reload gui"
+msgstr ""
+"Angegebenen Authentifikator tun ist nicht mehr vorhanden. Bitte laden gui"
+
+#: xmlrpc/auths/Authenticators.py:244
+msgid "BUG: Reached a point that should never have been reached!!!"
+msgstr "BUG: Erreicht einen Punkt, der nie zustande gekommen sind, sollten!!!"
+
+#: xmlrpc/osmanagers/OSManagers.py:129
+msgid "This os mnager is being used by deployed services"
+msgstr "Diese os Mnager ist von bereitgestellten Dienste verwendet wird"
+
+#: xmlrpc/osmanagers/OSManagers.py:145
+msgid "There is deployed services using this os manager"
+msgstr "Es gibt bereitgestellten Dienste mit dieser os-manager"
+
+#: xmlrpc/osmanagers/OSManagers.py:147
+msgid "Can't find os manager"
+msgstr "Nicht os Manager gefunden"
+
+#: xmlrpc/services/DeployedServices.py:52
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: xmlrpc/services/DeployedServices.py:112
+#: xmlrpc/services/DeployedServices.py:176
+#: xmlrpc/services/DeployedServices.py:195
+#: xmlrpc/services/DeployedServices.py:211
+#: xmlrpc/services/DeployedServices.py:225
+#: xmlrpc/services/DeployedServices.py:252
+#: xmlrpc/services/DeployedServices.py:266
+msgid "Deployed Service does not exists"
+msgstr "Bereitgestellten Diensts ist nicht vorhanden"
+
+#: xmlrpc/services/DeployedServices.py:209
+msgid "Group does not exists"
+msgstr "Gruppe existiert nicht"
+
+#: xmlrpc/services/DeployedServices.py:237
+msgid "Can't find deployed service"
+msgstr "Bereitgestellte Dienst nicht gefunden werden."
+
+#: xmlrpc/services/DeployedServices.py:250
+msgid "Transport does not exists"
+msgstr "Verkehr ist nicht vorhanden"
+
+#: xmlrpc/services/DeployedServices.py:281
+msgid "Deployed service does not exists"
+msgstr "Bereitgestellten Diensts ist nicht vorhanden"
+
+#: xmlrpc/services/ServiceProviders.py:154
+msgid "Can't delete service provider with services associated"
+msgstr "Service-Provider kann nicht mit verbundenen Services gelöscht werden"
+
+#: xmlrpc/services/ServiceProviders.py:157
+msgid "Can't locate the service provider"
+msgstr "Der Diensteanbieter kann nicht gefunden werden."
+
+#: xmlrpc/services/ServiceProviders.py:157 xmlrpc/services/Services.py:202
+#: xmlrpc/transports/Networks.py:70 xmlrpc/transports/Networks.py:78
+#: xmlrpc/transports/Networks.py:95
+msgid "Please, refresh interface"
+msgstr "Bitte aktualisieren Sie Schnittstelle"
+
+#: xmlrpc/services/Services.py:199
+msgid "Can't delete services with deployed services associated"
+msgstr ""
+"Dienstleistungen mit bereitgestellten Leistungen können nicht gelöscht "
+"werden."
+
+#: xmlrpc/services/Services.py:202
+msgid "Can't locate the service"
+msgstr "Der Dienst kann nicht gesucht werden."
+
+#: xmlrpc/services/UserDeployedServices.py:94
+#: xmlrpc/services/UserDeployedServices.py:110
+msgid "The deployed service is not active"
+msgstr "Der bereitgestellte Dienst ist nicht aktiv"
+
+#: xmlrpc/services/UserDeployedServices.py:97
+#: xmlrpc/services/UserDeployedServices.py:113
+msgid "This service don't allows assignations"
+msgstr "Dieser Dienst nicht ermöglicht Forderungsabtretungen"
+
+#: xmlrpc/services/UserDeployedServices.py:102
+#: xmlrpc/services/UserDeployedServices.py:120
+msgid "Deployed service not found!!! (refresh interface)"
+msgstr "Bereitgestellte Dienst nicht gefunden!!! (Aktualisieren Schnittstelle)"
+
+#: xmlrpc/services/UserDeployedServices.py:122
+msgid "User not found!!! (refresh interface)"
+msgstr "Benutzer nicht gefunden!!! (Aktualisieren Schnittstelle)"
+
+#: xmlrpc/services/UserDeployedServices.py:141
+msgid "No error"
+msgstr "Unbekannter Fehler"
+
+#: xmlrpc/services/UserDeployedServices.py:147
+msgid "User deployed service not found!!!"
+msgstr "Benutzer-Dienst nicht gefunden"
+
+#: xmlrpc/transports/Networks.py:70
+msgid "Can't locate the transport"
+msgstr "Den Transport kann nicht gefunden werden."
+
+#: xmlrpc/transports/Networks.py:78 xmlrpc/transports/Networks.py:95
+msgid "Can't locate the network"
+msgstr "Das Netzwerk kann nicht gefunden werden."
+
+#~ msgid "None OS Manager"
+#~ msgstr "Keine OS Manager"
+
+#~ msgid "Os Manager with no actions"
+#~ msgstr "OS-Manager keine Aktionen"
+
+#~ msgid "Base Service"
+#~ msgstr "Basisdienst"
+
+#~ msgid "None"
+#~ msgstr "Keine"
diff --git a/trunk/server/src/uds/locale/es/LC_MESSAGES/django.mo b/trunk/server/src/uds/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c22d800d
Binary files /dev/null and b/trunk/server/src/uds/locale/es/LC_MESSAGES/django.mo differ
diff --git a/trunk/server/src/uds/locale/es/LC_MESSAGES/django.po b/trunk/server/src/uds/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 00000000..50f46bfb
--- /dev/null
+++ b/trunk/server/src/uds/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,1801 @@
+# Translations for spanish
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Adolfo Gómez , 2012.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-28 10:17+0100\n"
+"PO-Revision-Date: 2012-11-28 10:28+0100\n"
+"Last-Translator: \n"
+"Language-Team: Spanish \n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"X-Generator: Lokalize 1.4\n"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/OVirt/OVirtProvider.py:91
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "Host"
+msgstr "Servidor "
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "VMWare VC Server IP or Hostname"
+msgstr "IP o nombre DNS del servidor VMWare VC"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid "Use SSL"
+msgstr "Usar SSL"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+msgid "If checked, will use a ssl connection to Active Directory"
+msgstr "Si está activada, utilizará una conexión ssl con Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Ldap User"
+msgstr "Usuario LDAP"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+msgid ""
+"Username with read privileges on the base selected (use USER@DOMAIN.DOM form "
+"for this)"
+msgstr ""
+"Usuario con derechos de lectura en la base seleccionada (utilice la forma "
+"USUARIO@DOMINIO.DOM para este elemento)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/ActiveDirectory_enterprise/Authenticator.py:50
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/RegexLdap/Authenticator.py:76
+#: auths/SimpleLDAP/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:77
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+#: services/OVirt/OVirtProvider.py:93
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67 web/forms/LoginForm.py:58
+msgid "Password"
+msgstr "Contraseña"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:53
+msgid "Password of the ldap user"
+msgstr "Contraseña del usuario del ldap"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout"
+msgstr "Espera "
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+msgid "Timeout in seconds of connection to LDAP"
+msgstr "Tiempo de espera en segundos"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:35
+msgid "Active Directory Authenticator"
+msgstr "Autenticador Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:37
+msgid "Authenticate against Active Directory"
+msgstr "Autenticador contra Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:46
+#: auths/EDirectory_enterprise/Authenticator.py:77
+#: auths/RegexLdap/Authenticator.py:72 auths/SimpleLDAP/Authenticator.py:73
+#: services/OVirt/OVirtProvider.py:92
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66 web/forms/LoginForm.py:57
+msgid "Username"
+msgstr "Usuario"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:48
+#: auths/EDirectory_enterprise/Authenticator.py:79
+#: auths/RegexLdap/Authenticator.py:74 auths/SAML_enterprise/SAML.py:113
+#: auths/SimpleLDAP/Authenticator.py:75
+msgid "Group"
+msgstr "Grupo"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:61
+#: auths/ActiveDirectory_enterprise/Authenticator.py:395
+msgid "Must specify the username in the form USERNAME@DOMAIN.DOM"
+msgstr ""
+"Debe especificar el nombre de usuario en la forma NOMBREUSUARIO@DOMINIO.DOM"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:127
+#: auths/EDirectory_enterprise/Authenticator.py:123
+#: auths/RegexLdap/Authenticator.py:159 auths/SimpleLDAP/Authenticator.py:158
+msgid "Ldap connection error: "
+msgstr "Error de conexión al ldap: "
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:299
+#: auths/ActiveDirectory_enterprise/Authenticator.py:345
+#: auths/EDirectory_enterprise/Authenticator.py:243
+#: auths/EDirectory_enterprise/Authenticator.py:286
+#: auths/RegexLdap/Authenticator.py:249 auths/RegexLdap/Authenticator.py:292
+#: auths/SimpleLDAP/Authenticator.py:269 auths/SimpleLDAP/Authenticator.py:313
+msgid "Username not found"
+msgstr "Nombre de usuario no hallado"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:332
+#: auths/SimpleLDAP/Authenticator.py:302
+msgid "Group not found"
+msgstr "Grupo no hallado"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:364
+#: auths/ActiveDirectory_enterprise/Authenticator.py:381
+#: auths/EDirectory_enterprise/Authenticator.py:303
+#: auths/RegexLdap/Authenticator.py:309 auths/SimpleLDAP/Authenticator.py:328
+#: auths/SimpleLDAP/Authenticator.py:342
+msgid "Too many results, be more specific"
+msgstr "Demasiados resultados, sea más específico"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:404
+msgid "Domain seems to be incorrect, please check it"
+msgstr "El dominio parece ser incorrecto, por favor, compruebelo"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:409
+msgid "Ldap does not seem an Active Directory (do not have user objects)"
+msgstr ""
+"El ldap indicado no parece un Active Directory (no tiene objetos de usuario)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:417
+msgid "Ldap does not seem an Active Directory (no not have group objects)"
+msgstr ""
+"El ldap indicado no parece un Active Directory (no no tienen objetos de "
+"grupo)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:425
+msgid ""
+"Ldap does not seem an Active Directory (do not have any user nor groups)"
+msgstr ""
+"El ldap indicado no parece un Active Directory (no tiene ningún usuario ni "
+"grupo)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:430
+#: auths/EDirectory_enterprise/Authenticator.py:358
+#: auths/RegexLdap/Authenticator.py:384 auths/SimpleLDAP/Authenticator.py:423
+msgid "Connection params seem correct, test was succesfully executed"
+msgstr ""
+"Los parámetros de conexión parecen correctos, la prueba fue ejecutado con "
+"exito"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "Port"
+msgstr "Puerto"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+msgid "Ldap port (389 for non ssl, 636 for ssl normally"
+msgstr "Puerto LDAP (389 para no SSL, 636 para ssl normalmente)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid ""
+"If checked, will use a ssl connection to ldap (if port is 389, will use in "
+"fact port 636)"
+msgstr ""
+"Si está activada, utilizará una conexión ssl con ldap (si el puerto es 389, "
+"utilizará de hecho el Puerto 636)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+msgid "Admin user"
+msgstr "Usuario Administrador"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+msgid "Username with read privileges on the eDirectory"
+msgstr "Usuario con privilegios de lectura sobre eDirectory"
+
+#: auths/EDirectory_enterprise/Authenticator.py:67
+msgid "eDirectory Authenticator"
+msgstr "Autenticador eDirectory"
+
+#: auths/EDirectory_enterprise/Authenticator.py:69
+msgid "Authenticate against eDirectory"
+msgstr "Autenticador contra eDirectory"
+
+#: auths/EDirectory_enterprise/Authenticator.py:323
+#: auths/RegexLdap/Authenticator.py:329 auths/SimpleLDAP/Authenticator.py:363
+msgid "Ldap search base is incorrect"
+msgstr "La base de búsqueda ldap es incorrecta"
+
+#: auths/EDirectory_enterprise/Authenticator.py:328
+#: auths/RegexLdap/Authenticator.py:334 auths/SimpleLDAP/Authenticator.py:368
+msgid "Ldap user class seems to be incorrect (no user found by that class)"
+msgstr ""
+"La clase de usuario de LDAP parece ser incorrecta (ningún usuario encontrado "
+"por esa clase)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:336
+#: auths/RegexLdap/Authenticator.py:350 auths/SimpleLDAP/Authenticator.py:384
+msgid ""
+"Ldap user id attribute seems to be incorrect (no user found by that "
+"attribute)"
+msgstr ""
+"El atributo de id de usuario de ldap parece ser incorrecto (ningún usuario "
+"encontrado por atributo)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:344
+msgid "Expected group attribute "
+msgstr "Atributo de grupo esperado "
+
+#: auths/EDirectory_enterprise/Authenticator.py:353
+msgid ""
+"Ldap user class or user id attr is probably wrong (Ldap is an eDirectory?)"
+msgstr ""
+"El atributo del id de usuario o la clase de usuario LDAP son probablemente "
+"incorrectos(¿el LDAP indicado es realmente un eDirectory?)"
+
+#: auths/IP/Authenticator.py:45 auths/IP/Authenticator.py:47
+msgid "IP Authenticator"
+msgstr "Autenticador por IP"
+
+#: auths/IP/Authenticator.py:51
+msgid "IP"
+msgstr "IP "
+
+#: auths/IP/Authenticator.py:52
+msgid "IP Range"
+msgstr "Intervalo IP"
+
+#: auths/IP/Authenticator.py:99 auths/InternalDB/Authenticator.py:102
+msgid "All seems fine in the authenticator."
+msgstr "Parace que todo funciona correctamente en el autenticador. "
+
+#: auths/InternalDB/Authenticator.py:46
+msgid "Internal Database"
+msgstr "Base de datos interna"
+
+#: auths/InternalDB/Authenticator.py:48
+msgid "Internal dabasase authenticator. Doesn't uses external sources"
+msgstr "Dabasase interna autenticador. No utiliza fuentes externas"
+
+#: auths/InternalDB/Authenticator.py:99
+msgid "Internal structures seems ok"
+msgstr "Las estructuras internas parecen correctas"
+
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Username with read privileges on the base selected"
+msgstr "Usuario con privilegios de lectura en la base elegida"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Base"
+msgstr "Base"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Common search base (used for \"users\" and \"groups\""
+msgstr "Base de búsqueda común (utilizado para \"los usuarios\" y \"grupos\""
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "User class"
+msgstr "Clase de usuario"
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "Class for LDAP users (normally posixAccount)"
+msgstr "Clase para los usuarios de LDAP (normalmente posixAccount)"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "User Id Attr"
+msgstr "Attr. de Id de usuario"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "Attribute that contains the user id"
+msgstr "Atributo que contiene el identificador de usuario"
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "User Name Attr"
+msgstr "Attr. de nombre usu."
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "Attributes that contains the user name (list of comma separated values)"
+msgstr ""
+"Atributos que contienen el nombre de usuario (lista de valores separados por "
+"comas)"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Group Name Attr"
+msgstr "Atr. de nombre de Grupo"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Attribute that contains the group name"
+msgstr "Atributo que contiene el nombre del grupo"
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Exp. for groups"
+msgstr "EXP. regular para grupos"
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Expression to extract the group name"
+msgstr "Expresión regular para extraer el nombre del grupo"
+
+#: auths/RegexLdap/Authenticator.py:62
+msgid "Regex LDAP Authenticator"
+msgstr "Autenticador Regex LDAP"
+
+#: auths/RegexLdap/Authenticator.py:64
+msgid "Regular Expressions LDAP authenticator"
+msgstr "Autenticador LDAP de expresiones regulares"
+
+#: auths/RegexLdap/Authenticator.py:96
+msgid "Invalid regular expression"
+msgstr "Expresión regular inválida"
+
+#: auths/RegexLdap/Authenticator.py:342 auths/SimpleLDAP/Authenticator.py:376
+msgid "Ldap group class seems to be incorrect (no group found by that class)"
+msgstr ""
+"La clase de grupo LDAP parece ser incorrecta (ningún grupo encontrado por "
+"esa clase)"
+
+#: auths/RegexLdap/Authenticator.py:360 auths/SimpleLDAP/Authenticator.py:392
+msgid ""
+"Ldap group id attribute seems to be incorrect (no group found by that "
+"attribute)"
+msgstr ""
+"Atributo de id de grupo ldap parece ser incorrecto (ningún grupo encontrado "
+"por atributo)"
+
+#: auths/RegexLdap/Authenticator.py:369 auths/SimpleLDAP/Authenticator.py:401
+msgid ""
+"Ldap user class or user id attr is probably wrong (can't find any user with "
+"both conditions)"
+msgstr ""
+"El atributo del id de usuario o la clase de usuario LDAP son probablemente "
+"incorrectos(no se puede encontrar ningún usuario con ambas condiciones)"
+
+#: auths/SAML_enterprise/SAML.py:79
+msgid "SAML Authenticator"
+msgstr "Autenticador SAML"
+
+#: auths/SAML_enterprise/SAML.py:91
+msgid "SAML (v2.0) Authenticator"
+msgstr "Autenticador SAML (versión 2.0)"
+
+#: auths/SAML_enterprise/SAML.py:110 templates/uds/internal_page.html:28
+msgid "User"
+msgstr "Usuario"
+
+#: auths/SAML_enterprise/SAML.py:119
+msgid "Private key"
+msgstr "Clave privada"
+
+#: auths/SAML_enterprise/SAML.py:120
+msgid ""
+"Private key used for sign and encription, as generated in base 64 from "
+"openssl"
+msgstr ""
+"Clave privada usada para la firma y la encriptación, en el formato que se "
+"genera en base64 por openssl"
+
+#: auths/SAML_enterprise/SAML.py:121
+msgid "Certificate"
+msgstr "Certificado"
+
+#: auths/SAML_enterprise/SAML.py:122
+msgid "Server certificate (public), , as generated in base 64 from openssl"
+msgstr ""
+"Certificado del servidor (público). Como se genera en base 62 desde openssl"
+
+#: auths/SAML_enterprise/SAML.py:123
+msgid "IDP Metadata"
+msgstr "Metadata del IDP"
+
+#: auths/SAML_enterprise/SAML.py:124
+msgid ""
+"You can enter here the URL or the IDP metadata or the metadata itself (xml)"
+msgstr ""
+"Puede insertar aqui la URL de los metadatos del IDP, o bien los metadatos "
+"mismos (en xml) "
+
+#: auths/SAML_enterprise/SAML.py:126
+msgid "Entity ID"
+msgstr "Entity ID"
+
+#: auths/SAML_enterprise/SAML.py:127
+msgid "ID of the SP. If left blank, this will be autogenerated from server URL"
+msgstr ""
+"ID del SP. Si se deja en blanco, será generado automáticamente usando el "
+"nombre de servidor utilizado para hacer login en la administración."
+
+#: auths/SAML_enterprise/SAML.py:129
+msgid "User name attrs"
+msgstr "Attrs. de nombre usu."
+
+#: auths/SAML_enterprise/SAML.py:130
+msgid "Fields from where to extract user name"
+msgstr "Expresión regular para extraer el nombre de usuario"
+
+#: auths/SAML_enterprise/SAML.py:132
+msgid "Group name attrs"
+msgstr "Atrs. de nombres de Grupos"
+
+#: auths/SAML_enterprise/SAML.py:133
+#, fuzzy
+msgid "Fields from where to extract the groups"
+msgstr "Expresión regular para extraer el nombre del grupo"
+
+#: auths/SAML_enterprise/SAML.py:135
+msgid "Real name attrs"
+msgstr "Attr. de nombre real"
+
+#: auths/SAML_enterprise/SAML.py:136
+msgid "Fields from where to extract the real name"
+msgstr "Expresión regular para extraer el nombre real del usuario"
+
+#: auths/SAML_enterprise/SAML.py:159
+msgid ""
+"Server certificate should be a valid PEM (PEM certificates starts with -----"
+"BEGIN CERTIFICATE-----)"
+msgstr ""
+"El certificado del servidor debe estarn en formato PEM (Los certificados en "
+"formato PEM empiezan por ---BEGIN CERTIFICATE---)"
+
+#: auths/SAML_enterprise/SAML.py:164
+msgid "Invalid server certificate. "
+msgstr "Certificado de servidor inválido"
+
+#: auths/SAML_enterprise/SAML.py:167
+msgid ""
+"Private key should be a valid PEM (PEM private keys starts with -----BEGIN "
+"RSA PRIVATE KEY-----"
+msgstr ""
+"La clave privada debe estar en formato PEM (los claves privadas en formato "
+"PEM comienzan por ---BEGIN RSA PRIVATE KEY---)"
+
+#: auths/SAML_enterprise/SAML.py:196
+msgid "Can't fetch url {0}: {1}"
+msgstr "No puedo obtener la url {0}: {1}"
+
+#: auths/SAML_enterprise/SAML.py:207
+msgid " (obtained from URL)"
+msgstr "(obtenido de la URL)"
+
+#: auths/SAML_enterprise/SAML.py:208
+msgid "XML do not seems valid for IDP Metadata "
+msgstr "El XML no parece ser válido para Metdatos de un IDP"
+
+#: auths/SAML_enterprise/SAML.py:224
+msgid "Can't access idp metadata"
+msgstr "No se puee acceder a los metadatos del IDP"
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Group class"
+msgstr "Clase de grupo"
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Class for LDAP groups (normally poxisGroup)"
+msgstr "Clase de grupos LDAP (normalmente poxisGroup)"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Group Id Attr"
+msgstr "Atr. id de Grupo"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Attribute that contains the group id"
+msgstr "Atributo que contiene el id de grupo"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Group membership attr"
+msgstr "Atr. de pertenencia de grupo"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Attribute of the group that contains the users belonging to it"
+msgstr "Atributo del grupo que contiene los usuarios pertenecientes al mismo"
+
+#: auths/SimpleLDAP/Authenticator.py:63
+msgid "SimpleLDAP Authenticator"
+msgstr "Autenticador LDAP Simple"
+
+#: auths/SimpleLDAP/Authenticator.py:65
+msgid "Simple LDAP authenticator"
+msgstr "Autenticador LDAP Simple"
+
+#: auths/SimpleLDAP/Authenticator.py:410
+msgid ""
+"Ldap group class or group id attr is probably wrong (can't find any group "
+"with both conditions)"
+msgstr ""
+"El atributo del identificador de clase o grupo de grupo LDAP son "
+"probablemente incorrectos (no se encuentra ningún grupo con ambas "
+"condiciones)"
+
+#: auths/SimpleLDAP/Authenticator.py:417
+msgid "Can't locate any group with the membership attribute specified"
+msgstr ""
+"No se puede localizar algún grupo con el atributo de pertenencia especificado"
+
+#: core/BaseModule.py:196
+msgid "No connection checking method is implemented."
+msgstr "No se ha implementado ningun metodo de comprobación de conexión"
+
+#: core/BaseModule.py:253
+msgid "No check method provided."
+msgstr "No se ha implementado ningún método de verificación."
+
+#: core/managers/PublicationManager.py:152
+msgid ""
+"Already publishing. Wait for previous publication to finish and try again"
+msgstr ""
+"Ya hay una publicación en curso. Espere a la finalización de esta o "
+"cancelela e intentelo de nuevo."
+
+#: core/managers/PublicationManager.py:165
+msgid "Can't cancel non running publication"
+msgstr "No se puede cancelar una publicación que no está activa"
+
+#: core/managers/PublicationManager.py:183
+msgid "Can't unpublish non usable publication"
+msgstr "No se puede despublicar una publicación no activa"
+
+#: core/managers/PublicationManager.py:186
+msgid "Can't unpublish publications with services in process"
+msgstr "No se puede despublicar con servicios en proceso"
+
+#: core/managers/UserPrefsManager.py:254
+msgid "Screen Size"
+msgstr "Tamaño de pantalla"
+
+#: core/managers/UserPrefsManager.py:258
+msgid "Full Screen"
+msgstr "Pantalla completa"
+
+#: core/managers/UserPrefsManager.py:261
+msgid "Screen colors"
+msgstr "Colores de pantalla"
+
+#: core/managers/UserPrefsManager.py:262
+msgid "8 bits"
+msgstr "8 bits"
+
+#: core/managers/UserPrefsManager.py:263
+msgid "16 bits"
+msgstr "16 bits"
+
+#: core/managers/UserPrefsManager.py:264
+msgid "24 bits"
+msgstr "24 bits"
+
+#: core/managers/UserPrefsManager.py:265
+msgid "32 bits"
+msgstr "32 bits"
+
+#: core/managers/UserServiceManager.py:303
+msgid "Can't cancel non running operation"
+msgstr "No se puede cancelar una operación que no está en curso"
+
+#: core/managers/UserServiceManager.py:322
+msgid "Can't remove a non active element"
+msgstr "No se puede eliminar un elemento que no está activo"
+
+#: core/managers/UserServiceManager.py:335
+msgid "Can't remove nor cancel {0} cause its states doesn't allows it"
+msgstr "No se puede eliminar o cancelar {0} porque sus estados no lo permiten"
+
+#: core/osmanagers/BaseOsManager.py:49
+msgid "Base OS Manager"
+msgstr "Gestor de OS Base"
+
+#: core/osmanagers/BaseOsManager.py:51
+msgid "Base Manager"
+msgstr "Gestor Base"
+
+#: core/transports/BaseTransport.py:113
+msgid "Transport empty"
+msgstr "Transporte Vacio"
+
+#: core/util/State.py:59
+msgid "Active"
+msgstr "Activo"
+
+#: core/util/State.py:59
+msgid "Inactive"
+msgstr "Inactivo"
+
+#: core/util/State.py:59
+msgid "Blocked"
+msgstr "Bloqueado"
+
+#: core/util/State.py:59
+msgid "Waiting publication"
+msgstr "En espera de publicación"
+
+#: core/util/State.py:60
+msgid "In preparation"
+msgstr "En preparación"
+
+#: core/util/State.py:60
+msgid "Valid"
+msgstr "Valido"
+
+#: core/util/State.py:61
+msgid "Waiting for removal"
+msgstr "Esperando eliminación"
+
+#: core/util/State.py:61
+msgid "Removing"
+msgstr "Eliminandose"
+
+#: core/util/State.py:61
+msgid "Removed"
+msgstr "Eliminado "
+
+#: core/util/State.py:61
+msgid "Canceled"
+msgstr "Cancelado"
+
+#: core/util/State.py:62
+msgid "Canceling"
+msgstr "Cancelando"
+
+#: core/util/State.py:62 templates/uds/error.html:6
+#: templates/uds/error.html.py:11
+msgid "Error"
+msgstr "Error"
+
+#: core/util/State.py:62
+msgid "Running"
+msgstr "En ejecución"
+
+#: core/util/State.py:62
+msgid "Finished"
+msgstr "Finalizado"
+
+#: core/util/State.py:62
+msgid "Waiting execution"
+msgstr "En espera de ejecución"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:45
+msgid "Linux OS Manager"
+msgstr "Gestor de S.O. Linux"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:47
+msgid ""
+"Os Manager to control linux virtual machines (basically renames machine and "
+"notify state)"
+msgstr ""
+"Gestor de s.o. para controlar maquinas virtuales con linux (basicamente "
+"renombra las máquinas y notifica los estados)"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "On Logout"
+msgstr "Al salir"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "What to do when user logout from service"
+msgstr "Que acción realizar cuando el usuario abandone el servicio"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:51
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:42
+msgid "Keep service assigned"
+msgstr "Mantener el servicio asignado"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:52
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:43
+msgid "Remove service"
+msgstr "Eliminar servicio"
+
+#: osmanagers/LinuxOsManager/__init__.py:43
+msgid "UDS Actor for linux machines (Requires python 2.6 or greater)"
+msgstr "Actor para las máquinas Linux (Precisa python 2.6 o mayor)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:23
+msgid "Windows Domain OS Manager"
+msgstr "Gestor para Windows con dominio"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:25
+msgid ""
+"Os Manager to control windows machines with domain. (Basically renames "
+"machine)"
+msgstr "Gestor de s.o. para controlar maquinas windows con dominio."
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid "Domain"
+msgstr "Dominio"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+msgid "Domain to join machines to (better use dns form of domain)"
+msgstr ""
+"Dominio al que unir las máquinas (es preferible utilizar la forma DNS del "
+"dominio)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "Account"
+msgstr "Cuenta"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+msgid "Account with rights to add machines to domain"
+msgstr "Cuenta con derecho para añadir máquinas al dominio"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+msgid "Password of the account"
+msgstr "Contraseña de la cuenta"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid "OU"
+msgstr "OU"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid ""
+"Organizational unit where to add machines in domain (check it before using "
+"it)"
+msgstr ""
+"Unidad organizativa donde crear las máquinas del dominio (compruebelo antes "
+"de utilizarlo)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:40
+msgid "Must provide a domain!!!"
+msgstr "Debe indicar un dominio!!!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:42
+msgid "Must provide an account to add machines to domain!!!"
+msgstr "Debe indicar una cuenta para añadir máquinas al dominio!!!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:44
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:41
+msgid "Must provide a password for the account!!!"
+msgstr "Debe indicar una contraseña para la cuenta!!!"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:23
+msgid "Windows Random Password OS Manager"
+msgstr "Gestor para windows con contraseña aleatoria"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:25
+msgid ""
+"Os Manager to control windows machines, with user password set randomly."
+msgstr ""
+"Gestor de s.o. para controlar maquinas windows con contraseñas aleatorias"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "User account to change password"
+msgstr "Cuenta de usuario a la que cambiar el password"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+msgid "Current (template) password of the user account"
+msgstr "Contraseña (la fijada en la plantilla) del usuario"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:39
+msgid "Must provide an user account!!!"
+msgstr "Debe indicar una cuenta de usuario!!!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:36
+msgid "Windows Basic OS Manager"
+msgstr "Gestor de SO Windows Básico"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:38
+msgid ""
+"Os Manager to control windows machines without domain. (Basically renames "
+"machine)"
+msgstr ""
+"Gestor de SO para controlar máquinas windows sin dominio. (Basicamente, "
+"renombra las máquinas)"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:52
+msgid "Length must be numeric!!"
+msgstr "La longitud debe ser numerica!!!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:54
+msgid "Length must be betwen 1 and six"
+msgstr "La longitud debe estar entre 1 y seis"
+
+#: osmanagers/WindowsOsManager/__init__.py:25
+msgid ""
+"UDS Actor for windows machines (Important!! Requires .net framework 3.5 "
+"sp1)"
+msgstr ""
+"Actor para las máquinas Windows (Importante!!! Requiere tener .net "
+"framework 3.5 sp1)"
+
+#: services/OVirt/OVirtLinkedService.py:56
+msgid "oVirt Linked Clone"
+msgstr "Servicio basado en oVirt Clones COW"
+
+#: services/OVirt/OVirtLinkedService.py:60
+msgid "oVirt Services based on templates and COW"
+msgstr "Servicioes oVirt basadoes en plantillas y COW"
+
+#: services/OVirt/OVirtLinkedService.py:77
+#: services/Vmware_enterprise/VCLinkedCloneService.py:70
+msgid "Number of desired machines to keep running waiting for a user"
+msgstr "Número de máquinas a manatener en ejecución esperando a un usuario"
+
+#: services/OVirt/OVirtLinkedService.py:83
+#: services/Vmware_enterprise/VCLinkedCloneService.py:72
+msgid "Number of desired machines to keep suspended waiting for use"
+msgstr "Número de maquinas a mantener suspendidas esperando asignación"
+
+#: services/OVirt/OVirtLinkedService.py:99
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+msgid "Base Machine"
+msgstr "Máquina de base"
+
+#: services/OVirt/OVirtLinkedService.py:99
+msgid "Service base machine"
+msgstr "Máquina de base para el servicio"
+
+#: services/OVirt/OVirtLinkedService.py:100
+msgid "Cluster"
+msgstr "Cluster"
+
+#: services/OVirt/OVirtLinkedService.py:106
+msgid "Cluster to contain services"
+msgstr "Cluster que contrendrá los servicios"
+
+#: services/OVirt/OVirtLinkedService.py:109
+msgid "Datastore Domain"
+msgstr "Almacenamiento"
+
+#: services/OVirt/OVirtLinkedService.py:110
+msgid "Datastore domain where to publish and put incrementals"
+msgstr "Almacenamiento donde colocar los incrementales y las publicaciones"
+
+#: services/OVirt/OVirtLinkedService.py:112
+#: services/Vmware_enterprise/VCLinkedCloneService.py:49
+msgid "Memory (Mb)"
+msgstr "Memoria (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:113
+msgid "Memory assigned to machines"
+msgstr "Memoria asignada a las máquinas"
+
+#: services/OVirt/OVirtLinkedService.py:115
+msgid "Memory Guaranteed (Mb)"
+msgstr "Memoria Garantizada (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:116
+msgid "Physical memory guaranteed to machines"
+msgstr "Memoria física garantizada a las máquinas"
+
+#: services/OVirt/OVirtLinkedService.py:118
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Machine Names"
+msgstr "Nombres de máquinas"
+
+#: services/OVirt/OVirtLinkedService.py:119
+#: services/Vmware_enterprise/VCLinkedCloneService.py:54
+msgid "Name Length"
+msgstr "Longitud del nombre"
+
+#: services/OVirt/OVirtLinkedService.py:120
+#: services/Vmware_enterprise/VCLinkedCloneService.py:55
+msgid "Length of numeric part for the names of this machines (betwen 3 and 6"
+msgstr ""
+"Longitud de la parte numérica de los nombres de esta maquinaria (entre 3 y 6"
+
+#: services/OVirt/OVirtLinkedService.py:122
+msgid "Display"
+msgstr "Pantalla"
+
+#: services/OVirt/OVirtLinkedService.py:123
+msgid "Display type (only for administration pourposses)"
+msgstr "Tipo de pantalla (solo para la administración)"
+
+#: services/OVirt/OVirtLinkedService.py:143
+#: services/Vmware_enterprise/VCLinkedCloneService.py:93
+msgid "The length of basename plus length must not be greater than 15"
+msgstr "La longitud de basename más longitud no debe ser superior a 15"
+
+#: services/OVirt/OVirtLinkedService.py:145
+#: services/Vmware_enterprise/VCLinkedCloneService.py:95
+msgid "The machine name can't be only numbers"
+msgstr "El nombre del equipo no puede ser sólo números"
+
+#: services/OVirt/OVirtProvider.py:72
+msgid "oVirt Platform Provider"
+msgstr "Proveedor para la plataforma oVirt"
+
+#: services/OVirt/OVirtProvider.py:76
+msgid "oVirt platform service provider"
+msgstr "Proveedor de servicios para la plataforma oVirt"
+
+#: services/OVirt/OVirtProvider.py:91
+msgid "oVirt Server IP or Hostname"
+msgstr "IP o nombre DNS del servidor oVirt"
+
+#: services/OVirt/OVirtProvider.py:92
+msgid "User with valid privileges on oVirt, (use \"user@domain\" form)"
+msgstr "Usuario con privilegios en oVirt (use la forma \"user@domain\")"
+
+#: services/OVirt/OVirtProvider.py:93
+msgid "Password of the user of oVirt"
+msgstr "Contraseña del usuario de oVirt"
+
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout in seconds of connection to VC"
+msgstr "Timeout en segundos"
+
+#: services/OVirt/OVirtProvider.py:95
+#: services/Vmware_enterprise/ServiceProviderVC.py:33
+msgid "Macs range"
+msgstr "Rango de Macs"
+
+#: services/OVirt/OVirtProvider.py:96
+#: services/Vmware_enterprise/ServiceProviderVC.py:34
+msgid "Range of valids macs for created machines"
+msgstr "Rango válido de macs para las máquinas creadas"
+
+#: services/OVirt/OVirtProvider.py:395
+msgid "Connection test successful"
+msgstr "Test de conexión correcto"
+
+#: services/OVirt/OVirtProvider.py:396
+#: services/Vmware_enterprise/ServiceProviderVC.py:120
+msgid "Connection failed. Check connection params"
+msgstr "La conexión ha fallado. Compruebe los parámetros."
+
+#: services/OVirt/OVirtPublication.py:85
+msgid "UDS pub for {0} at {1}"
+msgstr "Publicación de UDS para {0} en {1}"
+
+#: services/PhysicalMachines/IPMachineDeployed.py:57
+msgid "IP "
+msgstr "IP "
+
+#: services/PhysicalMachines/IPMachinesService.py:46
+msgid "List of IPS"
+msgstr "Lista de IPS"
+
+#: services/PhysicalMachines/IPMachinesService.py:49
+msgid "Physical machines accesed by ip"
+msgstr "Máquinas físicas de acceso por ip"
+
+#: services/PhysicalMachines/IPMachinesService.py:51
+msgid "This service provides access to POWERED-ON Machines by ip"
+msgstr "Este servicio provee acceso a máquinas ENCENDIDAS por IP"
+
+#: services/Sample/SampleProvider.py:138
+msgid "Methuselah is not alive!!! :-)"
+msgstr "Matusalén no está vivo!!! :-)"
+
+#: services/Sample/SampleProvider.py:178
+msgid "Nothing tested, but all went fine.."
+msgstr "Nada probado, pero todo salió bien..."
+
+#: services/Sample/SamplePublication.py:195
+msgid "Random integer was 9!!! :-)"
+msgstr "Entero aleatorio fue 9!!!!!! :-)"
+
+#: services/Vmware_enterprise/Helpers.py:72
+msgid "Local"
+msgstr "Local"
+
+#: services/Vmware_enterprise/Helpers.py:74
+msgid "Remote"
+msgstr "Remoto"
+
+#: services/Vmware_enterprise/PublicationVC.py:37
+msgid "Publication"
+msgstr "Publicación"
+
+#: services/Vmware_enterprise/PublicationVC.py:38
+msgid "UDS Publication for {0} created at {1}"
+msgstr "Publicación de UDS para {0} creada el {1}"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "VMWare VC Server Port (usually 443)"
+msgstr "Puerto del servidor VMWare VC (normalmente 443)"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+msgid "User with valid privileges on VC"
+msgstr "Usuario con privilegios validos sobre VC"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+msgid "Password of the user of the VC"
+msgstr "Contraseña del usuario de VC"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:39
+msgid "VMWare Virtual Center Provider"
+msgstr "Proveedor de VMWare Virtual Center"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:41
+msgid "Provides connection to Virtual Center Services"
+msgstr "Provee conexión a servicios basados en VMWare Virtual Center"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:110
+msgid "Error testing connection"
+msgstr "Error comprobando la conexión"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:113
+msgid "VmwareVC Provider: "
+msgstr "Proveedor VmwareVC:"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:119
+msgid "Connection params ok"
+msgstr "Parametros de conexión correctos"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:28
+msgid "Datacenter"
+msgstr "Datacenter "
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:34
+msgid "Datacenter containing base machine"
+msgstr "Datacenter que contiene la máquina de base"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:36
+msgid "Network"
+msgstr "Red"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:37
+msgid ""
+"If more than 1 interface is found in machine, use one on this network as main"
+msgstr ""
+"Si hay mas de un interfaz en la máquina virtual, use el que esté en esta red "
+"como principal."
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Pub. Resource Pool"
+msgstr "Pool de despliegue"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Resource Pool where deploy clones"
+msgstr "Pool de recursos donde desplegar los clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Clones Folder"
+msgstr "Carpeta de clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Folder where deploy clones"
+msgstr "Carpeta donde desplegar los clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:40
+msgid "Resource Pool"
+msgstr "Pool de recursos"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:46
+msgid "Resource Pool containing base machine"
+msgstr "Pool de recursos que contiene la máquina de base"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+msgid "Base machine for this service"
+msgstr "Máquina de base para este servicio"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:50
+msgid "Memory for machines deployed from this service"
+msgstr "Memoria para maquinas desplegadas desde este servicio"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:51
+msgid "Datastores"
+msgstr "Almacenamientos"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:52
+msgid "Datastores where to put incrementals"
+msgstr "Almacenamiento donde colocar los incrementales"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Base name for clones from this machine"
+msgstr "Nombre base para los clones de la máquina base"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:62
+msgid "VMWare Linked clone base"
+msgstr "Servicio basado en VMWare Linked clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:64
+msgid ""
+"This service provides access to Linked Clones machines on a Virtual Center"
+msgstr "Este servicio provee acceso a linked clones sobre Virtual Center"
+
+#: templates/404.html:4 templates/404.html.py:7
+msgid "Page not found"
+msgstr "Página no encontrada"
+
+#: templates/404.html:9
+msgid "Sorry, but the requested page could not be found."
+msgstr "Lo sentimos, pero no se encontró la página solicitada."
+
+#: templates/uds/base.html:7
+msgid "UDS"
+msgstr "UDS"
+
+#: templates/uds/detectJava.html:6
+msgid "Login redirection"
+msgstr "Redireccion del login"
+
+#: templates/uds/detectJava.html:38
+msgid "Go to main page"
+msgstr "Ir a la página principal"
+
+#: templates/uds/downloads.html:8 templates/uds/snippets/admin_user.html:7
+msgid "Downloads"
+msgstr "Descargas"
+
+#: templates/uds/downloads.html:11
+msgid ""
+"This page contains a list of downloadables provided by different modules"
+msgstr ""
+"Esta página contiene una lista de descargas proporcionadas por diferentes "
+"módulos"
+
+#: templates/uds/index.html:51
+msgid "Services"
+msgstr "Servicios"
+
+#: templates/uds/index.html:70
+msgid "Java not found"
+msgstr "Java no encontrado"
+
+#: templates/uds/index.html:71
+msgid ""
+"Java is not available on your browser, and the selected transport needs it."
+msgstr ""
+"Java no está disponible en el navegador, y el transporte seleccionado "
+"precisa de el."
+
+#: templates/uds/index.html:72
+msgid "Please, install latest version from"
+msgstr "Instale la versión mas reciente desde el"
+
+#: templates/uds/index.html:72
+msgid "Java website"
+msgstr "Sitio Web de Java"
+
+#: templates/uds/index.html:72
+msgid "and restart browser"
+msgstr "y reinicie el navegador"
+
+#: templates/uds/index.html:78
+msgid "Ip"
+msgstr "IP"
+
+#: templates/uds/index.html:79
+msgid "Networks"
+msgstr "Redes"
+
+#: templates/uds/index.html:80
+msgid "Transports"
+msgstr "Transportes"
+
+#: templates/uds/internal_page.html:34 templates/uds/prefs.html:12
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: templates/uds/internal_page.html:40
+msgid "Log out"
+msgstr "Desconectar"
+
+#: templates/uds/login.html:6
+msgid "Login to UDS"
+msgstr "Acceder a UDS"
+
+#: templates/uds/login.html:78
+msgid "Login"
+msgstr "Acceder"
+
+#: templates/uds/login.html:83
+msgid "Login data"
+msgstr "Datos de acceso"
+
+#: templates/uds/login.html:86
+msgid "Enter"
+msgstr "Entrar"
+
+#: templates/uds/login.html:93
+msgid "Back to login"
+msgstr "Volver a iniciar sesión"
+
+#: templates/uds/prefs.html:6
+msgid "UDS User Preferences"
+msgstr "UDS Preferencias de usuario "
+
+#: templates/uds/prefs.html:16
+msgid "Save Preferences"
+msgstr "Guardar Preferencias"
+
+#: templates/uds/service_not_ready.html:6
+msgid "Service not ready at this moment. Please, try again in a while."
+msgstr ""
+"El servicio no está disponible en estos momentos. Por favor, intentelo de "
+"nuevo pasado unos instantes."
+
+#: templates/uds/snippets/admin_user.html:4
+msgid "Admin"
+msgstr "Admin"
+
+#: templates/uds/snippets/back_to_list.html:3
+msgid "Back to services list"
+msgstr "Volver a la lista de servicios"
+
+#: templates/uds/snippets/lang.html:9
+msgid "Language"
+msgstr "Idioma"
+
+#: transports/NX/NXTransport.py:54
+msgid "NX Transport (direct)"
+msgstr "Transporte NX (directo)"
+
+#: transports/NX/NXTransport.py:56
+msgid "NX Transport for direct connection"
+msgstr "Transporte NX para conexión directa"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "Empty creds"
+msgstr "Sin credenciales"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "If checked, the credentials used to connect will be emtpy"
+msgstr ""
+"Si está activada, las credenciales utilizadas para conectar estarán vacías"
+
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66
+msgid "If not empty, this username will be always used as credential"
+msgstr ""
+"Si no está vacio, este nombre de usuario será utilizado como credencial fija"
+
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67
+msgid "If not empty, this password will be always used as credential"
+msgstr "Si no está vacio, este password será utiizado como credencial fija"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listen port"
+msgstr "Puerto de escucha"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listening port of NX (ssh) at client machine"
+msgstr "Puerto de escucha de NX (ssh) en el equipo cliente"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection"
+msgstr "Conexión"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection speed for this transport (quality)"
+msgstr "Velocidad de conexión de este transporte (calidad)"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Session"
+msgstr "Sesiones"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Desktop session"
+msgstr "Sesión de escritorio"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Disk Cache"
+msgstr "Caché de disco"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Cache size en Mb stored at disk"
+msgstr "Tamaño de ca Caché en MB almacenada en disco"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Memory Cache"
+msgstr "Memoria Caché"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Cache size en Mb keept at memory"
+msgstr "Tamaño del Caché en Mb a mantener en memoria"
+
+#: transports/NX/__init__.py:40 transports/TSNX/__init__.py:40
+msgid "NX Protocol"
+msgstr "Protocolo NX"
+
+#: transports/NX/__init__.py:46
+msgid "UDS Actor connector for NX (requires nomachine packages)"
+msgstr "Conector de UDS Actor para NX (requiere nomachine paquetes)"
+
+#: transports/NX/web.py:74
+msgid ""
+"In order to use this transport, you need to install first Nomachine Nx "
+"Client version 3.5.x"
+msgstr ""
+"Para poder utilizar este transporte, necesita instalar primera Nomachine Nx "
+"Cliente versión 3.5"
+
+#: transports/NX/web.py:75
+msgid "you can obtain it for your platform from"
+msgstr "Usted puede obtener para su plataforma de"
+
+#: transports/NX/web.py:75
+msgid "nochamine web site"
+msgstr "Sitio Web de Nomachine"
+
+#: transports/RDP/RDPTransport.py:30
+msgid "RDP Transport (direct)"
+msgstr "Transporte RDP (directo)"
+
+#: transports/RDP/RDPTransport.py:32
+msgid "RDP Transport for direct connection"
+msgstr "Transporte RDP para conexión directa"
+
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid ""
+"If not empty, this domain will be always used as credential (used as DOMAIN"
+"\\user)"
+msgstr ""
+"Si no está vacio, este domínio será usado como parte de las credenciales del "
+"usuario (usado como DOMAIN\\user)"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "Allow Smartcards"
+msgstr "Permitir tarjetas inteligentes"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "If checked, this transport will allow the use of smartcards"
+msgstr ""
+"Si está marcado, este transporte permitirá el uso de tarjetas inteligentes"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "Allow Printers"
+msgstr "Permitir impresoras"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "If checked, this transport will allow the use of user printers"
+msgstr ""
+"Si está marcado, este transporte permitirá el uso de impresoras remotas"
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "Allow Drives"
+msgstr "Permitir unidades"
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "If checked, this transport will allow the use of user drives"
+msgstr ""
+"Si está marcado, este transporte permitirá la redireccion de las unidades "
+"locales a la máquina remota"
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "Allow Serials"
+msgstr "Permitir series"
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "If checked, this transport will allow the use of user serial ports"
+msgstr ""
+"Si está marcado, este transporte permitirá la redirección de puertos serie"
+
+#: transports/RDP/TSRDPTransport.py:31
+msgid "RDP Transport (tunneled)"
+msgstr "Transporte RDP (vía túnel)"
+
+#: transports/RDP/TSRDPTransport.py:33
+msgid "RDP Transport for tunneled connection"
+msgstr "Transporte RDP para conexión vía túnel"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid "Tunnel server"
+msgstr "Servidor de túnel"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid ""
+"IP or Hostname of tunnel server send to client device (\"public\" ip) and "
+"port. (use HOST:PORT format)"
+msgstr ""
+"IP o nombre de host del servidor de túnel enviar a dispositivo de cliente "
+"(ip \"pública\") y puerto. (utilice el formato HOST: puerto)"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid "Tunnel host check"
+msgstr "Verificación de host de túnel"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid ""
+"If not empty, this server will be used to check if service is running before "
+"assigning it to user. (use HOST:PORT format)"
+msgstr ""
+"Si no vacía, este servidor se utilizará para comprobar si el servicio se "
+"ejecuta antes de asignarle al usuario. (utilice el formato HOST: puerto)"
+
+#: transports/RDP/__init__.py:17
+msgid "Remote Desktop Protocol"
+msgstr "Protocolo de Escritorio remoto (RDP)"
+
+#: transports/RDP/web.py:83
+msgid "In order to use this service, you should first install CoRD."
+msgstr "Para poder utilizar este servicio, primero debe instalar CoRD."
+
+#: transports/RDP/web.py:84 transports/RGS-enterprise/web.py:82
+msgid "You can obtain it from"
+msgstr "Puede obtenerlo de"
+
+#: transports/RDP/web.py:84
+msgid "CoRD Website"
+msgstr "Sitio Web de CoRD"
+
+#: transports/RGS-enterprise/RGSTransport.py:34
+msgid "RGS Transport (direct)"
+msgstr "Transporte RGS (directo)"
+
+#: transports/RGS-enterprise/RGSTransport.py:36
+msgid "RGS Transport for direct connection"
+msgstr "Transporte RGS para conexión directa"
+
+#: transports/RGS-enterprise/RGSTransport.py:45
+#: transports/RGS-enterprise/TRGSTransport.py:50
+msgid "Image quality"
+msgstr "Calidad de imagen"
+
+#: transports/RGS-enterprise/RGSTransport.py:46
+#: transports/RGS-enterprise/TRGSTransport.py:51
+msgid "Quality of image codec (0-100)"
+msgstr "Calidad del códec de imagen (0-100)"
+
+#: transports/RGS-enterprise/RGSTransport.py:47
+#: transports/RGS-enterprise/TRGSTransport.py:52
+msgid "Adjustable Quality"
+msgstr "Calidad ajustable"
+
+#: transports/RGS-enterprise/RGSTransport.py:48
+#: transports/RGS-enterprise/TRGSTransport.py:53
+msgid "If checked, the image quality will be adjustable with bandwidth"
+msgstr ""
+"Si está activada, la calidad de imagen será ajustable con ancho de banda"
+
+#: transports/RGS-enterprise/RGSTransport.py:49
+#: transports/RGS-enterprise/TRGSTransport.py:54
+msgid "Min. Adjustable Quality"
+msgstr "Mín. calidad ajustable"
+
+#: transports/RGS-enterprise/RGSTransport.py:50
+#: transports/RGS-enterprise/TRGSTransport.py:55
+msgid ""
+"The lowest image quality applied to images to maintain the minimum update "
+"rate."
+msgstr ""
+"La menor calidad de imagen aplicada a las imágenes para mantener la "
+"actualización mínima tasa."
+
+#: transports/RGS-enterprise/RGSTransport.py:51
+#: transports/RGS-enterprise/TRGSTransport.py:56
+msgid "Adjustable Frame Rate"
+msgstr "Ajustable velocidad de fotogramas"
+
+#: transports/RGS-enterprise/RGSTransport.py:52
+#: transports/RGS-enterprise/TRGSTransport.py:57
+msgid "Update rate threshold to begin adjusting image quality"
+msgstr ""
+"Umbral de velocidad de actualización para comenzar el ajuste de calidad de "
+"imagen"
+
+#: transports/RGS-enterprise/RGSTransport.py:53
+#: transports/RGS-enterprise/TRGSTransport.py:58
+msgid "Match Local Resolution"
+msgstr "Resolución Local de partido"
+
+#: transports/RGS-enterprise/RGSTransport.py:54
+#: transports/RGS-enterprise/TRGSTransport.py:59
+msgid ""
+"Change the Sender's resolution to match the Receiver's resolution when "
+"connecting"
+msgstr ""
+"Cambiar la resolución del remitente para que coincida con la resolución del "
+"receptor cuando conexión"
+
+#: transports/RGS-enterprise/RGSTransport.py:55
+#: transports/RGS-enterprise/TRGSTransport.py:60
+msgid "Redirect USB"
+msgstr "Redirección USB"
+
+#: transports/RGS-enterprise/RGSTransport.py:56
+#: transports/RGS-enterprise/TRGSTransport.py:61
+msgid "If checked, the USB will be redirected."
+msgstr "Si está activada, se redirigirá el USB."
+
+#: transports/RGS-enterprise/RGSTransport.py:57
+#: transports/RGS-enterprise/TRGSTransport.py:62
+msgid "Redirect Audio"
+msgstr "Redirección de Audio"
+
+#: transports/RGS-enterprise/RGSTransport.py:58
+#: transports/RGS-enterprise/TRGSTransport.py:63
+msgid "If checked, the Audio will be redirected."
+msgstr "Si está activado, el audio será redirigido al cliente"
+
+#: transports/RGS-enterprise/RGSTransport.py:59
+#: transports/RGS-enterprise/TRGSTransport.py:64
+msgid "Redirect Mic"
+msgstr "Redirigir Mic"
+
+#: transports/RGS-enterprise/RGSTransport.py:60
+#: transports/RGS-enterprise/TRGSTransport.py:65
+msgid "If checked, the Mic will be redirected."
+msgstr "Si está activado. el micro será redirigido al cliente"
+
+#: transports/RGS-enterprise/TRGSTransport.py:36
+msgid "RGS Transport (tunneled)"
+msgstr "Transporte RGS (vía túnel)"
+
+#: transports/RGS-enterprise/TRGSTransport.py:38
+msgid "RGS Transport for tunneled connection"
+msgstr "Transporte RGS para conexión vía túnel"
+
+#: transports/RGS-enterprise/web.py:81
+msgid "In order to use this service, you should first install RGS Receiver."
+msgstr ""
+"Para poder utilizar este servicio, primero debe instalar el Receiver de RGS"
+
+#: transports/RGS-enterprise/web.py:82
+msgid "HP Website"
+msgstr "Sitio Web de HP"
+
+#: transports/TSNX/TSNXTransport.py:55
+msgid "NX Transport (tunneled)"
+msgstr "Transporte NX (vía túnel)"
+
+#: transports/TSNX/TSNXTransport.py:57
+msgid "NX Transport for tunneled connection"
+msgstr "Transporte NX para conexión vía túnel"
+
+#: web/errors.py:58
+msgid "Unknown error"
+msgstr "Error desconocido"
+
+#: web/errors.py:59
+msgid "Transport not found"
+msgstr "Transporte no hallado"
+
+#: web/errors.py:60
+msgid "Service not found"
+msgstr "Servicio no hallado"
+
+#: web/errors.py:61 xmlrpc/auths/AdminAuth.py:171
+msgid "Access denied"
+msgstr "Acceso denegado"
+
+#: web/errors.py:62
+msgid ""
+"Invalid service. The service is not available at this moment. Please, try "
+"later"
+msgstr ""
+"Servicio invalido. El servicio no está disponible en estos momentos. Por "
+"favor, intentelo de nuevo pasado unos instantes."
+
+#: web/errors.py:63
+msgid "Maximum services limit reached. Please, contact administrator"
+msgstr ""
+"Número máximo de servicios alcanzado. Por favor, contacte con su "
+"administrador."
+
+#: web/errors.py:64
+msgid "You need to enable cookies to let this application work"
+msgstr "Necesita habilitar los cookies para permitir funcionar esta aplicación"
+
+#: web/errors.py:65
+msgid "User service not found"
+msgstr "Servicio de usuario no hallado"
+
+#: web/errors.py:66
+msgid "Authenticator not found"
+msgstr "Autenticador no hallado"
+
+#: web/errors.py:67 xmlrpc/auths/AdminAuth.py:169
+msgid "Invalid authenticator"
+msgstr "Autenticador Invalido"
+
+#: web/views.py:334
+msgid "Authenticator do not provides information"
+msgstr "El autenticador no proporciona información alguna"
+
+#: web/forms/LoginForm.py:59
+msgid "Authenticator"
+msgstr "Autenticador"
+
+#: xmlrpc/auths/AdminAuth.py:113
+msgid "Credentials no longer valid"
+msgstr "Las credenciales ya no son válidas"
+
+#: xmlrpc/auths/AdminAuth.py:149
+msgid "Administration"
+msgstr "Administración"
+
+#: xmlrpc/auths/AdminAuth.py:164
+msgid "Invalid credentials"
+msgstr "Credenciales Invalidas"
+
+#: xmlrpc/auths/Authenticators.py:106
+msgid "Authenticator does not exists"
+msgstr "El autenticador no existe"
+
+#: xmlrpc/auths/Authenticators.py:161 xmlrpc/osmanagers/OSManagers.py:115
+#: xmlrpc/services/ServiceProviders.py:115
+#: xmlrpc/services/ServiceProviders.py:139 xmlrpc/services/Services.py:160
+#: xmlrpc/services/Services.py:184 xmlrpc/transports/Networks.py:86
+#: xmlrpc/transports/Networks.py:97
+#, python-format
+msgid "Name %s already exists"
+msgstr "El nombre %s ya existe"
+
+#: xmlrpc/auths/Authenticators.py:234
+msgid "Authenticator do not supports search"
+msgstr "El autenticador no soporta búsquedas"
+
+#: xmlrpc/auths/Authenticators.py:240
+msgid "Specified authenticator do not exists anymore. Please, reload gui"
+msgstr ""
+"Hacer autenticador especificado no existe ya. Por favor, vuelva a cargar la "
+"interfaz gráfica de usuario"
+
+#: xmlrpc/auths/Authenticators.py:244
+msgid "BUG: Reached a point that should never have been reached!!!"
+msgstr "BUG: Llegado a un punto que no debe nunca han alcanzado!!!"
+
+#: xmlrpc/osmanagers/OSManagers.py:129
+msgid "This os mnager is being used by deployed services"
+msgstr "Este OS Manager está siendo utilizado por servicios desplegados"
+
+#: xmlrpc/osmanagers/OSManagers.py:145
+msgid "There is deployed services using this os manager"
+msgstr "Existen servicios desplegados que utilizan este gestor de SO"
+
+#: xmlrpc/osmanagers/OSManagers.py:147
+msgid "Can't find os manager"
+msgstr "No se puede hallar el gestor de SO"
+
+#: xmlrpc/services/DeployedServices.py:52
+msgid "Unknown"
+msgstr "Desconocido"
+
+#: xmlrpc/services/DeployedServices.py:112
+#: xmlrpc/services/DeployedServices.py:176
+#: xmlrpc/services/DeployedServices.py:195
+#: xmlrpc/services/DeployedServices.py:211
+#: xmlrpc/services/DeployedServices.py:225
+#: xmlrpc/services/DeployedServices.py:252
+#: xmlrpc/services/DeployedServices.py:266
+msgid "Deployed Service does not exists"
+msgstr "El servicio desplegado no existe"
+
+#: xmlrpc/services/DeployedServices.py:209
+msgid "Group does not exists"
+msgstr "El grupo no existe"
+
+#: xmlrpc/services/DeployedServices.py:237
+msgid "Can't find deployed service"
+msgstr "No puedo hallar el servicio desplegado"
+
+#: xmlrpc/services/DeployedServices.py:250
+msgid "Transport does not exists"
+msgstr "El transporte no existe"
+
+#: xmlrpc/services/DeployedServices.py:281
+msgid "Deployed service does not exists"
+msgstr "El servicio desplegado no existe"
+
+#: xmlrpc/services/ServiceProviders.py:154
+msgid "Can't delete service provider with services associated"
+msgstr "No se puede borrar un proveedor de servicios con servicios asociados"
+
+#: xmlrpc/services/ServiceProviders.py:157
+msgid "Can't locate the service provider"
+msgstr "No puedo hallar el proveedor de servicios"
+
+#: xmlrpc/services/ServiceProviders.py:157 xmlrpc/services/Services.py:202
+#: xmlrpc/transports/Networks.py:70 xmlrpc/transports/Networks.py:78
+#: xmlrpc/transports/Networks.py:95
+msgid "Please, refresh interface"
+msgstr "Por favor, refresque la interfaz."
+
+#: xmlrpc/services/Services.py:199
+msgid "Can't delete services with deployed services associated"
+msgstr "No se puede borrar un servicio con servicios desplegados asociados"
+
+#: xmlrpc/services/Services.py:202
+msgid "Can't locate the service"
+msgstr "No puedo hallar el servicio"
+
+#: xmlrpc/services/UserDeployedServices.py:94
+#: xmlrpc/services/UserDeployedServices.py:110
+msgid "The deployed service is not active"
+msgstr "El servicio desplegado no está activo"
+
+#: xmlrpc/services/UserDeployedServices.py:97
+#: xmlrpc/services/UserDeployedServices.py:113
+msgid "This service don't allows assignations"
+msgstr "El servicio no admite asignaciones"
+
+#: xmlrpc/services/UserDeployedServices.py:102
+#: xmlrpc/services/UserDeployedServices.py:120
+msgid "Deployed service not found!!! (refresh interface)"
+msgstr "Servicio desplegado no hallado!!! (refresque la interfaz)"
+
+#: xmlrpc/services/UserDeployedServices.py:122
+msgid "User not found!!! (refresh interface)"
+msgstr "Usuario no hallado (refresque la interfaz)"
+
+#: xmlrpc/services/UserDeployedServices.py:141
+msgid "No error"
+msgstr "Sin errores"
+
+#: xmlrpc/services/UserDeployedServices.py:147
+msgid "User deployed service not found!!!"
+msgstr "Servicio de usuario no hallado!!!"
+
+#: xmlrpc/transports/Networks.py:70
+msgid "Can't locate the transport"
+msgstr "No puedo hallar el transporte"
+
+#: xmlrpc/transports/Networks.py:78 xmlrpc/transports/Networks.py:95
+msgid "Can't locate the network"
+msgstr "No puedo hallar la red"
+
+#~ msgid "None OS Manager"
+#~ msgstr "Ningún gestor de OS"
+
+#~ msgid "Os Manager with no actions"
+#~ msgstr "Gestor de OS que no realiza ninguna acción"
+
+#~ msgid "Base Service"
+#~ msgstr "Servicio Base"
+
+#~ msgid "None"
+#~ msgstr "Ninguno"
diff --git a/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.mo b/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..eea0332b
Binary files /dev/null and b/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.po b/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..f68c67b7
--- /dev/null
+++ b/trunk/server/src/uds/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,1855 @@
+# Translations for french
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# Adolfo Gómez , 2012.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-28 10:17+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/OVirt/OVirtProvider.py:91
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "Host"
+msgstr "Serveur"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:29
+#: auths/EDirectory_enterprise/Authenticator.py:60
+#: auths/RegexLdap/Authenticator.py:49 auths/SimpleLDAP/Authenticator.py:49
+#: services/Vmware_enterprise/ServiceProviderVC.py:28
+msgid "VMWare VC Server IP or Hostname"
+msgstr "Le nom DNS ou l'adresse IP VC VMWare Server"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid "Use SSL"
+msgstr "Utiliser SSL"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:30
+msgid "If checked, will use a ssl connection to Active Directory"
+msgstr "Si elle est cochée, va utiliser une connexion SSL à Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Ldap User"
+msgstr "Utilisateur LDAP"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:31
+msgid ""
+"Username with read privileges on the base selected (use USER@DOMAIN.DOM form "
+"for this)"
+msgstr ""
+"Nom d'utilisateur avec des privilèges lire sur la base choisie (utiliser le "
+"format USER@DOMAIN.DOMpour cela)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/ActiveDirectory_enterprise/Authenticator.py:50
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/RegexLdap/Authenticator.py:76
+#: auths/SimpleLDAP/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:77
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+#: services/OVirt/OVirtProvider.py:93
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67 web/forms/LoginForm.py:58
+msgid "Password"
+msgstr "Mot de passe"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:32
+#: auths/EDirectory_enterprise/Authenticator.py:64
+#: auths/RegexLdap/Authenticator.py:53 auths/SimpleLDAP/Authenticator.py:53
+msgid "Password of the ldap user"
+msgstr "Mot de passe de l'utilisateur ldap"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout"
+msgstr "Temporisation"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:33
+#: auths/EDirectory_enterprise/Authenticator.py:65
+#: auths/RegexLdap/Authenticator.py:54 auths/SimpleLDAP/Authenticator.py:54
+msgid "Timeout in seconds of connection to LDAP"
+msgstr "Délai en secondes de la connexion à LDAP"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:35
+msgid "Active Directory Authenticator"
+msgstr "Active Directory authentificateur"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:37
+#, fuzzy
+msgid "Authenticate against Active Directory"
+msgstr "Authentificateur sur Active Directory"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:46
+#: auths/EDirectory_enterprise/Authenticator.py:77
+#: auths/RegexLdap/Authenticator.py:72 auths/SimpleLDAP/Authenticator.py:73
+#: services/OVirt/OVirtProvider.py:92
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66 web/forms/LoginForm.py:57
+msgid "Username"
+msgstr "Nom d'utilisateur"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:48
+#: auths/EDirectory_enterprise/Authenticator.py:79
+#: auths/RegexLdap/Authenticator.py:74 auths/SAML_enterprise/SAML.py:113
+#: auths/SimpleLDAP/Authenticator.py:75
+msgid "Group"
+msgstr "Groupe"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:61
+#: auths/ActiveDirectory_enterprise/Authenticator.py:395
+msgid "Must specify the username in the form USERNAME@DOMAIN.DOM"
+msgstr ""
+"Doit spécifier le nom d'utilisateur sous la forme NOMDEUTILISEUR@DOMAINE.DOM"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:127
+#: auths/EDirectory_enterprise/Authenticator.py:123
+#: auths/RegexLdap/Authenticator.py:159 auths/SimpleLDAP/Authenticator.py:158
+msgid "Ldap connection error: "
+msgstr "Erreur de connexion LDAP : "
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:299
+#: auths/ActiveDirectory_enterprise/Authenticator.py:345
+#: auths/EDirectory_enterprise/Authenticator.py:243
+#: auths/EDirectory_enterprise/Authenticator.py:286
+#: auths/RegexLdap/Authenticator.py:249 auths/RegexLdap/Authenticator.py:292
+#: auths/SimpleLDAP/Authenticator.py:269 auths/SimpleLDAP/Authenticator.py:313
+msgid "Username not found"
+msgstr "Nom d'utilisateur introuvable"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:332
+#: auths/SimpleLDAP/Authenticator.py:302
+msgid "Group not found"
+msgstr "Groupe introuvable"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:364
+#: auths/ActiveDirectory_enterprise/Authenticator.py:381
+#: auths/EDirectory_enterprise/Authenticator.py:303
+#: auths/RegexLdap/Authenticator.py:309 auths/SimpleLDAP/Authenticator.py:328
+#: auths/SimpleLDAP/Authenticator.py:342
+msgid "Too many results, be more specific"
+msgstr "Trop de résultats, être plus spécifique"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:404
+msgid "Domain seems to be incorrect, please check it"
+msgstr "Domaine semble incorrect, veuillez vérifier"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:409
+msgid "Ldap does not seem an Active Directory (do not have user objects)"
+msgstr ""
+"LDAP ne semble pas un serveur Active Directory (n'ont pas les objets "
+"utilisateur)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:417
+msgid "Ldap does not seem an Active Directory (no not have group objects)"
+msgstr ""
+"LDAP ne semble pas un serveur Active Directory (ne pas ont les objets de "
+"groupe)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:425
+msgid ""
+"Ldap does not seem an Active Directory (do not have any user nor groups)"
+msgstr ""
+"LDAP ne semble pas un serveur Active Directory (n'ont pas tout utilisateur "
+"ni les groupes)"
+
+#: auths/ActiveDirectory_enterprise/Authenticator.py:430
+#: auths/EDirectory_enterprise/Authenticator.py:358
+#: auths/RegexLdap/Authenticator.py:384 auths/SimpleLDAP/Authenticator.py:423
+msgid "Connection params seem correct, test was succesfully executed"
+msgstr ""
+"Connexion params semblent correctes, le test a été correctement exécutée"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "Port"
+msgstr "Port"
+
+#: auths/EDirectory_enterprise/Authenticator.py:61
+#: auths/RegexLdap/Authenticator.py:50 auths/SimpleLDAP/Authenticator.py:50
+msgid "Ldap port (389 for non ssl, 636 for ssl normally"
+msgstr "Port LDAP (389 non SSL, 636 pour ssl normalement"
+
+#: auths/EDirectory_enterprise/Authenticator.py:62
+#: auths/RegexLdap/Authenticator.py:51 auths/SimpleLDAP/Authenticator.py:51
+msgid ""
+"If checked, will use a ssl connection to ldap (if port is 389, will use in "
+"fact port 636)"
+msgstr ""
+"Si cochée, utilise une connexion ssl à ldap (si le port est 389, utilisera "
+"dans port de fait 636)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+#, fuzzy
+msgid "Admin user"
+msgstr "Admin"
+
+#: auths/EDirectory_enterprise/Authenticator.py:63
+#, fuzzy
+msgid "Username with read privileges on the eDirectory"
+msgstr ""
+"Nom d'utilisateur avec des privilèges de lecture sur la base sélectionnée"
+
+#: auths/EDirectory_enterprise/Authenticator.py:67
+#, fuzzy
+msgid "eDirectory Authenticator"
+msgstr "Active Directory authentificateur"
+
+#: auths/EDirectory_enterprise/Authenticator.py:69
+#, fuzzy
+msgid "Authenticate against eDirectory"
+msgstr "Authentificateur sur Active Directory"
+
+#: auths/EDirectory_enterprise/Authenticator.py:323
+#: auths/RegexLdap/Authenticator.py:329 auths/SimpleLDAP/Authenticator.py:363
+msgid "Ldap search base is incorrect"
+msgstr "Base de recherche LDAP est incorrect"
+
+#: auths/EDirectory_enterprise/Authenticator.py:328
+#: auths/RegexLdap/Authenticator.py:334 auths/SimpleLDAP/Authenticator.py:368
+msgid "Ldap user class seems to be incorrect (no user found by that class)"
+msgstr ""
+"Classe d'utilisateur LDAP semble incorrect (aucun utilisateur ne trouvé par "
+"cette classe)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:336
+#: auths/RegexLdap/Authenticator.py:350 auths/SimpleLDAP/Authenticator.py:384
+msgid ""
+"Ldap user id attribute seems to be incorrect (no user found by that "
+"attribute)"
+msgstr ""
+"Attribut d'id utilisateur LDAP semble incorrect (aucun utilisateur ne "
+"trouvée par qui attribut)"
+
+#: auths/EDirectory_enterprise/Authenticator.py:344
+msgid "Expected group attribute "
+msgstr "Attribut du groupe prévu "
+
+#: auths/EDirectory_enterprise/Authenticator.py:353
+#, fuzzy
+msgid ""
+"Ldap user class or user id attr is probably wrong (Ldap is an eDirectory?)"
+msgstr ""
+"LDAP user utilisateur ou la classe id attr est probablement erroné (ne peut "
+"pas trouver n'importe quel utilisateur avec les deux conditions)"
+
+#: auths/IP/Authenticator.py:45 auths/IP/Authenticator.py:47
+msgid "IP Authenticator"
+msgstr "Authentificateur IP"
+
+#: auths/IP/Authenticator.py:51
+msgid "IP"
+msgstr "IP "
+
+#: auths/IP/Authenticator.py:52
+msgid "IP Range"
+msgstr "Plage d'adresses IP"
+
+#: auths/IP/Authenticator.py:99 auths/InternalDB/Authenticator.py:102
+msgid "All seems fine in the authenticator."
+msgstr "Tout semble fine dans l'authentificateur."
+
+#: auths/InternalDB/Authenticator.py:46
+msgid "Internal Database"
+msgstr "Base de données interne"
+
+#: auths/InternalDB/Authenticator.py:48
+msgid "Internal dabasase authenticator. Doesn't uses external sources"
+msgstr "Dabasase interne authentificateur. N'utilise des sources externes"
+
+#: auths/InternalDB/Authenticator.py:99
+msgid "Internal structures seems ok"
+msgstr "Les structures internes semble ok"
+
+#: auths/RegexLdap/Authenticator.py:52 auths/SimpleLDAP/Authenticator.py:52
+msgid "Username with read privileges on the base selected"
+msgstr ""
+"Nom d'utilisateur avec des privilèges de lecture sur la base sélectionnée"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Base"
+msgstr "Base"
+
+#: auths/RegexLdap/Authenticator.py:55 auths/SimpleLDAP/Authenticator.py:55
+msgid "Common search base (used for \"users\" and \"groups\""
+msgstr ""
+"Recherche commune base (utilisé pour les « utilisateurs » et « groupes »"
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "User class"
+msgstr "Classe utilisateur"
+
+#: auths/RegexLdap/Authenticator.py:56 auths/SimpleLDAP/Authenticator.py:56
+msgid "Class for LDAP users (normally posixAccount)"
+msgstr "Classe pour les utilisateurs de LDAP (normalement posixAccount)"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "User Id Attr"
+msgstr "Utilisateur Id Attr"
+
+#: auths/RegexLdap/Authenticator.py:57 auths/SimpleLDAP/Authenticator.py:57
+msgid "Attribute that contains the user id"
+msgstr "Attribut qui contient l'id utilisateur"
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "User Name Attr"
+msgstr "Utilisateur nom Attr"
+
+#: auths/RegexLdap/Authenticator.py:58 auths/SimpleLDAP/Authenticator.py:58
+msgid "Attributes that contains the user name (list of comma separated values)"
+msgstr ""
+"Attributs qui contient le nom d'utilisateur (liste de valeurs séparées par "
+"des virgules)"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Group Name Attr"
+msgstr "Groupe nom Attr"
+
+#: auths/RegexLdap/Authenticator.py:59
+msgid "Attribute that contains the group name"
+msgstr "Attribut qui contient le nom du groupe"
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Exp. for groups"
+msgstr "Exp régulière pour les groupes"
+
+#: auths/RegexLdap/Authenticator.py:60
+msgid "Regular Expression to extract the group name"
+msgstr "Expression régulière pour extraire le nom du groupe"
+
+#: auths/RegexLdap/Authenticator.py:62
+msgid "Regex LDAP Authenticator"
+msgstr "Authentificateur LDAP Regex"
+
+#: auths/RegexLdap/Authenticator.py:64
+msgid "Regular Expressions LDAP authenticator"
+msgstr "Authentificateur de LDAP d'Expressions régulière"
+
+#: auths/RegexLdap/Authenticator.py:96
+msgid "Invalid regular expression"
+msgstr ""
+
+#: auths/RegexLdap/Authenticator.py:342 auths/SimpleLDAP/Authenticator.py:376
+msgid "Ldap group class seems to be incorrect (no group found by that class)"
+msgstr ""
+"Classe de groupe LDAP semble incorrect (aucun groupe ne trouvée par cette "
+"classe)"
+
+#: auths/RegexLdap/Authenticator.py:360 auths/SimpleLDAP/Authenticator.py:392
+msgid ""
+"Ldap group id attribute seems to be incorrect (no group found by that "
+"attribute)"
+msgstr ""
+"Attribut d'id groupe LDAP semble incorrect (aucun groupe ne trouvée par qui "
+"attribut)"
+
+#: auths/RegexLdap/Authenticator.py:369 auths/SimpleLDAP/Authenticator.py:401
+msgid ""
+"Ldap user class or user id attr is probably wrong (can't find any user with "
+"both conditions)"
+msgstr ""
+"LDAP user utilisateur ou la classe id attr est probablement erroné (ne peut "
+"pas trouver n'importe quel utilisateur avec les deux conditions)"
+
+#: auths/SAML_enterprise/SAML.py:79
+#, fuzzy
+msgid "SAML Authenticator"
+msgstr "Authentificateur"
+
+#: auths/SAML_enterprise/SAML.py:91
+#, fuzzy
+msgid "SAML (v2.0) Authenticator"
+msgstr "Authentificateur"
+
+#: auths/SAML_enterprise/SAML.py:110 templates/uds/internal_page.html:28
+msgid "User"
+msgstr "Utilisateur"
+
+#: auths/SAML_enterprise/SAML.py:119
+msgid "Private key"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:120
+msgid ""
+"Private key used for sign and encription, as generated in base 64 from "
+"openssl"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:121
+msgid "Certificate"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:122
+msgid "Server certificate (public), , as generated in base 64 from openssl"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:123
+msgid "IDP Metadata"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:124
+msgid ""
+"You can enter here the URL or the IDP metadata or the metadata itself (xml)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:126
+msgid "Entity ID"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:127
+msgid "ID of the SP. If left blank, this will be autogenerated from server URL"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:129
+#, fuzzy
+msgid "User name attrs"
+msgstr "Utilisateur nom Attr"
+
+#: auths/SAML_enterprise/SAML.py:130
+#, fuzzy
+msgid "Fields from where to extract user name"
+msgstr "Expression régulière pour extraire le nom du groupe"
+
+#: auths/SAML_enterprise/SAML.py:132
+#, fuzzy
+msgid "Group name attrs"
+msgstr "Groupe nom Attr"
+
+#: auths/SAML_enterprise/SAML.py:133
+#, fuzzy
+msgid "Fields from where to extract the groups"
+msgstr "Expression régulière pour extraire le nom du groupe"
+
+#: auths/SAML_enterprise/SAML.py:135
+#, fuzzy
+msgid "Real name attrs"
+msgstr "Utilisateur nom Attr"
+
+#: auths/SAML_enterprise/SAML.py:136
+#, fuzzy
+msgid "Fields from where to extract the real name"
+msgstr "Expression régulière pour extraire le nom du groupe"
+
+#: auths/SAML_enterprise/SAML.py:159
+msgid ""
+"Server certificate should be a valid PEM (PEM certificates starts with -----"
+"BEGIN CERTIFICATE-----)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:164
+msgid "Invalid server certificate. "
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:167
+msgid ""
+"Private key should be a valid PEM (PEM private keys starts with -----BEGIN "
+"RSA PRIVATE KEY-----"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:196
+msgid "Can't fetch url {0}: {1}"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:207
+msgid " (obtained from URL)"
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:208
+msgid "XML do not seems valid for IDP Metadata "
+msgstr ""
+
+#: auths/SAML_enterprise/SAML.py:224
+msgid "Can't access idp metadata"
+msgstr ""
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Group class"
+msgstr "Groupe classe"
+
+#: auths/SimpleLDAP/Authenticator.py:59
+msgid "Class for LDAP groups (normally poxisGroup)"
+msgstr "Classe pour les groupes LDAP (normalement poxisGroup)"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Group Id Attr"
+msgstr "Groupe Id Attr"
+
+#: auths/SimpleLDAP/Authenticator.py:60
+msgid "Attribute that contains the group id"
+msgstr "Attribut qui contient l'id de groupe"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Group membership attr"
+msgstr "Groupe adhésion attr"
+
+#: auths/SimpleLDAP/Authenticator.py:61
+msgid "Attribute of the group that contains the users belonging to it"
+msgstr "Attribut du groupe qui contient les utilisateurs appartenant à elle"
+
+#: auths/SimpleLDAP/Authenticator.py:63
+msgid "SimpleLDAP Authenticator"
+msgstr "Authentificateur SimpleLDAP"
+
+#: auths/SimpleLDAP/Authenticator.py:65
+msgid "Simple LDAP authenticator"
+msgstr "Simple authentificateur LDAP"
+
+#: auths/SimpleLDAP/Authenticator.py:410
+msgid ""
+"Ldap group class or group id attr is probably wrong (can't find any group "
+"with both conditions)"
+msgstr ""
+"LDAP groupe classe ou groupe id attr est probablement erroné (ne peut pas "
+"trouver n'importe quel groupe avec les deux conditions)"
+
+#: auths/SimpleLDAP/Authenticator.py:417
+msgid "Can't locate any group with the membership attribute specified"
+msgstr ""
+"Ne peut pas localiser n'importe quel groupe avec l'attribut d'appartenance "
+"spécifié"
+
+#: core/BaseModule.py:196
+msgid "No connection checking method is implemented."
+msgstr "Aucun lien vérifier la méthode n'est implémentée."
+
+#: core/BaseModule.py:253
+msgid "No check method provided."
+msgstr "Aucune méthode de vérification fournie."
+
+#: core/managers/PublicationManager.py:152
+msgid ""
+"Already publishing. Wait for previous publication to finish and try again"
+msgstr ""
+"Déjà la publication. Attendez une publication antérieure de terminer et "
+"réessayez"
+
+#: core/managers/PublicationManager.py:165
+msgid "Can't cancel non running publication"
+msgstr "Ne peut annuler la publication non courante"
+
+#: core/managers/PublicationManager.py:183
+msgid "Can't unpublish non usable publication"
+msgstr "Ne peut annuler la publication publication non utilisable"
+
+#: core/managers/PublicationManager.py:186
+msgid "Can't unpublish publications with services in process"
+msgstr ""
+"Ne peut annuler la publication des publications avec services de processus"
+
+#: core/managers/UserPrefsManager.py:254
+msgid "Screen Size"
+msgstr "Taille de l'écran"
+
+#: core/managers/UserPrefsManager.py:258
+msgid "Full Screen"
+msgstr "Plein écran"
+
+#: core/managers/UserPrefsManager.py:261
+msgid "Screen colors"
+msgstr "Couleurs de l'écran"
+
+#: core/managers/UserPrefsManager.py:262
+msgid "8 bits"
+msgstr "8 bits"
+
+#: core/managers/UserPrefsManager.py:263
+msgid "16 bits"
+msgstr "16 bits"
+
+#: core/managers/UserPrefsManager.py:264
+msgid "24 bits"
+msgstr "24 bits"
+
+#: core/managers/UserPrefsManager.py:265
+msgid "32 bits"
+msgstr "32 bits"
+
+#: core/managers/UserServiceManager.py:303
+msgid "Can't cancel non running operation"
+msgstr "Ne peut annuler une opération non courante"
+
+#: core/managers/UserServiceManager.py:322
+msgid "Can't remove a non active element"
+msgstr "Impossible de supprimer un élément non actif"
+
+#: core/managers/UserServiceManager.py:335
+msgid "Can't remove nor cancel {0} cause its states doesn't allows it"
+msgstr "Ne peut pas supprimer ni annuler {0} cause ses États ne lui permet pas"
+
+#: core/osmanagers/BaseOsManager.py:49
+msgid "Base OS Manager"
+msgstr "Gestionnaire d'OS de base"
+
+#: core/osmanagers/BaseOsManager.py:51
+msgid "Base Manager"
+msgstr "Gestionnaire de base de"
+
+#: core/transports/BaseTransport.py:113
+msgid "Transport empty"
+msgstr "Transport vide"
+
+#: core/util/State.py:59
+msgid "Active"
+msgstr "Active"
+
+#: core/util/State.py:59
+msgid "Inactive"
+msgstr "Inactif"
+
+#: core/util/State.py:59
+msgid "Blocked"
+msgstr "Bloqué"
+
+#: core/util/State.py:59
+msgid "Waiting publication"
+msgstr "Attente de publication"
+
+#: core/util/State.py:60
+msgid "In preparation"
+msgstr "En préparation"
+
+#: core/util/State.py:60
+msgid "Valid"
+msgstr "Valide"
+
+#: core/util/State.py:61
+msgid "Waiting for removal"
+msgstr "Attente d'enlèvement"
+
+#: core/util/State.py:61
+msgid "Removing"
+msgstr "Suppression"
+
+#: core/util/State.py:61
+msgid "Removed"
+msgstr "Supprimé"
+
+#: core/util/State.py:61
+msgid "Canceled"
+msgstr "Annulée"
+
+#: core/util/State.py:62
+msgid "Canceling"
+msgstr "Annulation"
+
+#: core/util/State.py:62 templates/uds/error.html:6
+#: templates/uds/error.html.py:11
+msgid "Error"
+msgstr "Erreur"
+
+#: core/util/State.py:62
+msgid "Running"
+msgstr "En cours d'exécution"
+
+#: core/util/State.py:62
+msgid "Finished"
+msgstr "Fini"
+
+#: core/util/State.py:62
+msgid "Waiting execution"
+msgstr "Exécution en attente"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:45
+msgid "Linux OS Manager"
+msgstr "Gestionnaire de système d'exploitation Linux"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:47
+msgid ""
+"Os Manager to control linux virtual machines (basically renames machine and "
+"notify state)"
+msgstr ""
+"Gestionnaire de l'os pour contrôler les machines virtuelles de linux "
+"(essentiellement renomme machine et notifier l'État)"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "On Logout"
+msgstr "Sur Logout"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:50
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:41
+msgid "What to do when user logout from service"
+msgstr "Que faire quand déconnexion de l'utilisateur du service"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:51
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:42
+msgid "Keep service assigned"
+msgstr "Garder les services affectés"
+
+#: osmanagers/LinuxOsManager/LinuxOsManager.py:52
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:43
+msgid "Remove service"
+msgstr "Retirer du service"
+
+#: osmanagers/LinuxOsManager/__init__.py:43
+msgid "UDS Actor for linux machines (Requires python 2.6 or greater)"
+msgstr ""
+"Acteur de l'UDS pour linux machines (nécessite le python 2.6 ou plus)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:23
+msgid "Windows Domain OS Manager"
+msgstr "Gestionnaire de système d'exploitation pour le domaine Windows"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:25
+msgid ""
+"Os Manager to control windows machines with domain. (Basically renames "
+"machine)"
+msgstr ""
+"Os Manager pour contrôler les machines windows avec le domaine. "
+"(Essentiellement renomme machine)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid "Domain"
+msgstr "Domaine"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:29
+msgid "Domain to join machines to (better use dns form of domain)"
+msgstr ""
+"Domaine de rejoindre les machines (mieux utiliser le formulaire de dns du "
+"domaine)"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "Account"
+msgstr "Compte"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:30
+msgid "Account with rights to add machines to domain"
+msgstr "Compte avec droits d'ajouter des machines à domaine"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:31
+msgid "Password of the account"
+msgstr "Mot de passe du compte"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid "OU"
+msgstr "OU"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:32
+msgid ""
+"Organizational unit where to add machines in domain (check it before using "
+"it)"
+msgstr ""
+"Unité organisationnelle où ajouter des machines dans le domaine (vérifier il "
+"avant d'utiliser GTG"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:40
+msgid "Must provide a domain!!!"
+msgstr "Doit fournir un domaine!!!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:42
+msgid "Must provide an account to add machines to domain!!!"
+msgstr "Doit fournir un compte pour ajouter des machines à domaine!!!"
+
+#: osmanagers/WindowsOsManager/WinDomainOsManager.py:44
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:41
+msgid "Must provide a password for the account!!!"
+msgstr "Doit fournir un mot de passe du compte!!!"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:23
+#, fuzzy
+msgid "Windows Random Password OS Manager"
+msgstr "Gestionnaire de système d'exploitation pour le domaine Windows"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:25
+#, fuzzy
+msgid ""
+"Os Manager to control windows machines, with user password set randomly."
+msgstr ""
+"Os Manager pour contrôler les machines windows avec le domaine. "
+"(Essentiellement renomme machine)"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:29
+msgid "User account to change password"
+msgstr ""
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:30
+#, fuzzy
+msgid "Current (template) password of the user account"
+msgstr "Doit fournir un mot de passe du compte!!!"
+
+#: osmanagers/WindowsOsManager/WinRandomPassOsManager.py:39
+#, fuzzy
+msgid "Must provide an user account!!!"
+msgstr "Doit fournir un mot de passe du compte!!!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:36
+msgid "Windows Basic OS Manager"
+msgstr "Gestionnaire de base de Windows OS"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:38
+msgid ""
+"Os Manager to control windows machines without domain. (Basically renames "
+"machine)"
+msgstr ""
+"Os Manager pour contrôler les machines windows sans domaine. "
+"(Essentiellement renomme machine)"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:52
+msgid "Length must be numeric!!"
+msgstr "La longueur doit être numérique!!"
+
+#: osmanagers/WindowsOsManager/WindowsOsManager.py:54
+msgid "Length must be betwen 1 and six"
+msgstr "La longueur doit être images 1 et six"
+
+#: osmanagers/WindowsOsManager/__init__.py:25
+msgid ""
+"UDS Actor for windows machines (Important!! Requires .net framework 3.5 "
+"sp1)"
+msgstr ""
+"Acteur de l'UDS pour machines windows (Important!! Nécessite le .net "
+"framework 3.5 SP1)"
+
+#: services/OVirt/OVirtLinkedService.py:56
+#, fuzzy
+msgid "oVirt Linked Clone"
+msgstr "Base de clone lié VMWare"
+
+#: services/OVirt/OVirtLinkedService.py:60
+msgid "oVirt Services based on templates and COW"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:77
+#: services/Vmware_enterprise/VCLinkedCloneService.py:70
+msgid "Number of desired machines to keep running waiting for a user"
+msgstr "Nombre de machines désirés de courir d'attente pour un utilisateur."
+
+#: services/OVirt/OVirtLinkedService.py:83
+#: services/Vmware_enterprise/VCLinkedCloneService.py:72
+msgid "Number of desired machines to keep suspended waiting for use"
+msgstr ""
+"Nombre de machines désirés pour garder suspendu en attente pour utilisation"
+
+#: services/OVirt/OVirtLinkedService.py:99
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+#, fuzzy
+msgid "Base Machine"
+msgstr "Gestionnaire de base de"
+
+#: services/OVirt/OVirtLinkedService.py:99
+#, fuzzy
+msgid "Service base machine"
+msgstr "Machine de base"
+
+#: services/OVirt/OVirtLinkedService.py:100
+msgid "Cluster"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:106
+msgid "Cluster to contain services"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:109
+#, fuzzy
+msgid "Datastore Domain"
+msgstr "Magasins de données"
+
+#: services/OVirt/OVirtLinkedService.py:110
+#, fuzzy
+msgid "Datastore domain where to publish and put incrementals"
+msgstr "Magasins de données où mettre des sauvegardes incrémentielles"
+
+#: services/OVirt/OVirtLinkedService.py:112
+#: services/Vmware_enterprise/VCLinkedCloneService.py:49
+msgid "Memory (Mb)"
+msgstr "Mémoire (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:113
+msgid "Memory assigned to machines"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:115
+#, fuzzy
+msgid "Memory Guaranteed (Mb)"
+msgstr "Mémoire (Mb)"
+
+#: services/OVirt/OVirtLinkedService.py:116
+msgid "Physical memory guaranteed to machines"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:118
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Machine Names"
+msgstr "Noms de machine"
+
+#: services/OVirt/OVirtLinkedService.py:119
+#: services/Vmware_enterprise/VCLinkedCloneService.py:54
+msgid "Name Length"
+msgstr "Longueur du nom"
+
+#: services/OVirt/OVirtLinkedService.py:120
+#: services/Vmware_enterprise/VCLinkedCloneService.py:55
+msgid "Length of numeric part for the names of this machines (betwen 3 and 6"
+msgstr ""
+"Longueur de la partie numérique pour les noms de ces machines (images 3 et 6"
+
+#: services/OVirt/OVirtLinkedService.py:122
+msgid "Display"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:123
+msgid "Display type (only for administration pourposses)"
+msgstr ""
+
+#: services/OVirt/OVirtLinkedService.py:143
+#: services/Vmware_enterprise/VCLinkedCloneService.py:93
+msgid "The length of basename plus length must not be greater than 15"
+msgstr ""
+"La longueur du nom de base plus la longueur ne doit pas être supérieure à 15"
+
+#: services/OVirt/OVirtLinkedService.py:145
+#: services/Vmware_enterprise/VCLinkedCloneService.py:95
+msgid "The machine name can't be only numbers"
+msgstr "Nom de l'ordinateur ne peut pas être uniquement des nombres"
+
+#: services/OVirt/OVirtProvider.py:72
+#, fuzzy
+msgid "oVirt Platform Provider"
+msgstr "VMWare Virtual Center fournisseur"
+
+#: services/OVirt/OVirtProvider.py:76
+#, fuzzy
+msgid "oVirt platform service provider"
+msgstr "Impossible de trouver le fournisseur de services"
+
+#: services/OVirt/OVirtProvider.py:91
+#, fuzzy
+msgid "oVirt Server IP or Hostname"
+msgstr "Le nom DNS ou l'adresse IP VC VMWare Server"
+
+#: services/OVirt/OVirtProvider.py:92
+#, fuzzy
+msgid "User with valid privileges on oVirt, (use \"user@domain\" form)"
+msgstr "Utilisateur avec des privilèges valides sur VC"
+
+#: services/OVirt/OVirtProvider.py:93
+#, fuzzy
+msgid "Password of the user of oVirt"
+msgstr "Mot de passe de l'utilisateur de la Croix de Victoria"
+
+#: services/OVirt/OVirtProvider.py:94
+#: services/Vmware_enterprise/ServiceProviderVC.py:32
+msgid "Timeout in seconds of connection to VC"
+msgstr "Délai en secondes de connexion à VC"
+
+#: services/OVirt/OVirtProvider.py:95
+#: services/Vmware_enterprise/ServiceProviderVC.py:33
+msgid "Macs range"
+msgstr "Gamme Mac"
+
+#: services/OVirt/OVirtProvider.py:96
+#: services/Vmware_enterprise/ServiceProviderVC.py:34
+msgid "Range of valids macs for created machines"
+msgstr "Gamme de Mac valides pour les machines créés"
+
+#: services/OVirt/OVirtProvider.py:395
+msgid "Connection test successful"
+msgstr ""
+
+#: services/OVirt/OVirtProvider.py:396
+#: services/Vmware_enterprise/ServiceProviderVC.py:120
+msgid "Connection failed. Check connection params"
+msgstr "Échec de la connexion. Vérifiez la connexion params"
+
+#: services/OVirt/OVirtPublication.py:85
+#, fuzzy
+msgid "UDS pub for {0} at {1}"
+msgstr "Publication de l'UDS pour {0} créé à {1}"
+
+#: services/PhysicalMachines/IPMachineDeployed.py:57
+msgid "IP "
+msgstr "IP "
+
+#: services/PhysicalMachines/IPMachinesService.py:46
+msgid "List of IPS"
+msgstr "Liste des IPS"
+
+#: services/PhysicalMachines/IPMachinesService.py:49
+msgid "Physical machines accesed by ip"
+msgstr "Accesed de machines physiques par ip"
+
+#: services/PhysicalMachines/IPMachinesService.py:51
+msgid "This service provides access to POWERED-ON Machines by ip"
+msgstr "Ce service permet d'accéder aux machines SOUS-TENSION par ip"
+
+#: services/Sample/SampleProvider.py:138
+msgid "Methuselah is not alive!!! :-)"
+msgstr "Mathusalem n'est pas vivant!!! :-)"
+
+#: services/Sample/SampleProvider.py:178
+msgid "Nothing tested, but all went fine.."
+msgstr "Rien à l'épreuve, mais tout s'est bien passé..."
+
+#: services/Sample/SamplePublication.py:195
+msgid "Random integer was 9!!! :-)"
+msgstr "Nombre entier aléatoire était 9!!! :-)"
+
+#: services/Vmware_enterprise/Helpers.py:72
+msgid "Local"
+msgstr "Local"
+
+#: services/Vmware_enterprise/Helpers.py:74
+msgid "Remote"
+msgstr "Distant"
+
+#: services/Vmware_enterprise/PublicationVC.py:37
+msgid "Publication"
+msgstr "Publication"
+
+#: services/Vmware_enterprise/PublicationVC.py:38
+#, fuzzy
+msgid "UDS Publication for {0} created at {1}"
+msgstr "Publication de l'UDS pour {0} créé à {1}"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:29
+msgid "VMWare VC Server Port (usually 443)"
+msgstr "Port du serveur VMWare VC (habituellement 443)"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:30
+#, fuzzy
+msgid "User with valid privileges on VC"
+msgstr ""
+"Nom d'utilisateur avec des privilèges de lecture sur la base sélectionnée"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:31
+#, fuzzy
+msgid "Password of the user of the VC"
+msgstr "Mot de passe de l'utilisateur de la Croix de Victoria"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:39
+#, fuzzy
+msgid "VMWare Virtual Center Provider"
+msgstr "VmwareVC fournisseur : "
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:41
+msgid "Provides connection to Virtual Center Services"
+msgstr "Fournit la connexion aux Services du Centre virtuel"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:110
+msgid "Error testing connection"
+msgstr "Connexion essai erreur"
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:113
+msgid "VmwareVC Provider: "
+msgstr "VmwareVC fournisseur : "
+
+#: services/Vmware_enterprise/ServiceProviderVC.py:119
+msgid "Connection params ok"
+msgstr "Connexion params ok"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:28
+msgid "Datacenter"
+msgstr "Datacenter"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:34
+msgid "Datacenter containing base machine"
+msgstr "Machine de base contenant Datacenter"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:36
+msgid "Network"
+msgstr "Réseau"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:37
+msgid ""
+"If more than 1 interface is found in machine, use one on this network as main"
+msgstr ""
+"Si plus de 1 interface est trouvé dans la machine, utilisez l'une sur ce "
+"réseau comme principal"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Pub. Resource Pool"
+msgstr "Pub. Ressource Pool"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:38
+msgid "Resource Pool where deploy clones"
+msgstr "Ressource Pool déployer où les clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Clones Folder"
+msgstr "Dossier de clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:39
+msgid "Folder where deploy clones"
+msgstr "Dossier où déployer clones"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:40
+msgid "Resource Pool"
+msgstr "Ressource Pool"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:46
+msgid "Resource Pool containing base machine"
+msgstr "Machine base contenant de ressource Pool"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:48
+msgid "Base machine for this service"
+msgstr "Machine de base pour ce service."
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:50
+msgid "Memory for machines deployed from this service"
+msgstr "Mémoire pour les machines déployés à partir de ce service"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:51
+#, fuzzy
+msgid "Datastores"
+msgstr "Magasins de données"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:52
+#, fuzzy
+msgid "Datastores where to put incrementals"
+msgstr "Magasins de données où mettre des sauvegardes incrémentielles"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:53
+msgid "Base name for clones from this machine"
+msgstr "Nom de base des clones de cette machine."
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:62
+#, fuzzy
+msgid "VMWare Linked clone base"
+msgstr "Base de clone lié VMWare"
+
+#: services/Vmware_enterprise/VCLinkedCloneService.py:64
+msgid ""
+"This service provides access to Linked Clones machines on a Virtual Center"
+msgstr ""
+"Ce service donne accès aux machines de Clones liés sur un centre virtuel"
+
+#: templates/404.html:4 templates/404.html.py:7
+msgid "Page not found"
+msgstr "Page non trouvée"
+
+#: templates/404.html:9
+msgid "Sorry, but the requested page could not be found."
+msgstr "Désolé, mais la page demandée n'a pas pu être trouvée."
+
+#: templates/uds/base.html:7
+msgid "UDS"
+msgstr "UDS"
+
+#: templates/uds/detectJava.html:6
+msgid "Login redirection"
+msgstr ""
+
+#: templates/uds/detectJava.html:38
+msgid "Go to main page"
+msgstr ""
+
+#: templates/uds/downloads.html:8 templates/uds/snippets/admin_user.html:7
+msgid "Downloads"
+msgstr "Téléchargements"
+
+#: templates/uds/downloads.html:11
+msgid ""
+"This page contains a list of downloadables provided by different modules"
+msgstr ""
+"Cette page contient une liste de téléchargeables fournis par différents "
+"modules"
+
+#: templates/uds/index.html:51
+msgid "Services"
+msgstr "Services"
+
+#: templates/uds/index.html:70
+msgid "Java not found"
+msgstr "Java non trouvé"
+
+#: templates/uds/index.html:71
+msgid ""
+"Java is not available on your browser, and the selected transport needs it."
+msgstr ""
+"Java n'est pas disponible sur votre navigateur, et le transport sélectionné "
+"en a besoin."
+
+#: templates/uds/index.html:72
+msgid "Please, install latest version from"
+msgstr "Veuillez installer une version plus récente de"
+
+#: templates/uds/index.html:72
+msgid "Java website"
+msgstr "Site Web Java"
+
+#: templates/uds/index.html:72
+msgid "and restart browser"
+msgstr "Redémarrez le navigateur"
+
+#: templates/uds/index.html:78
+msgid "Ip"
+msgstr "IP"
+
+#: templates/uds/index.html:79
+msgid "Networks"
+msgstr "Réseaux"
+
+#: templates/uds/index.html:80
+msgid "Transports"
+msgstr "Transports"
+
+#: templates/uds/internal_page.html:34 templates/uds/prefs.html:12
+msgid "Preferences"
+msgstr "Préférences"
+
+#: templates/uds/internal_page.html:40
+msgid "Log out"
+msgstr "Déconnexion"
+
+#: templates/uds/login.html:6
+msgid "Login to UDS"
+msgstr "Connexion à UDS"
+
+#: templates/uds/login.html:78
+msgid "Login"
+msgstr "Login"
+
+#: templates/uds/login.html:83
+msgid "Login data"
+msgstr "Données de connexion"
+
+#: templates/uds/login.html:86
+msgid "Enter"
+msgstr "Entrez"
+
+#: templates/uds/login.html:93
+msgid "Back to login"
+msgstr "Retour à la connexion"
+
+#: templates/uds/prefs.html:6
+msgid "UDS User Preferences"
+msgstr "Préférences de l'utilisateur UDS"
+
+#: templates/uds/prefs.html:16
+msgid "Save Preferences"
+msgstr "Enregistrer les préférences"
+
+#: templates/uds/service_not_ready.html:6
+msgid "Service not ready at this moment. Please, try again in a while."
+msgstr ""
+"Le service n'est pas prêt à ce moment. S'il vous plaît, essayez à nouveau de "
+"temps en temps."
+
+#: templates/uds/snippets/admin_user.html:4
+msgid "Admin"
+msgstr "Admin"
+
+#: templates/uds/snippets/back_to_list.html:3
+msgid "Back to services list"
+msgstr "Retour à la liste de services"
+
+#: templates/uds/snippets/lang.html:9
+msgid "Language"
+msgstr "Langue"
+
+#: transports/NX/NXTransport.py:54
+msgid "NX Transport (direct)"
+msgstr "NX Transport (direct)"
+
+#: transports/NX/NXTransport.py:56
+msgid "NX Transport for direct connection"
+msgstr "NX Transport pour une connexion directe"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "Empty creds"
+msgstr "Références vide"
+
+#: transports/NX/NXTransport.py:60 transports/RDP/RDPTransport.py:36
+#: transports/RDP/TSRDPTransport.py:40
+#: transports/RGS-enterprise/RGSTransport.py:41
+#: transports/RGS-enterprise/TRGSTransport.py:46
+#: transports/TSNX/TSNXTransport.py:65
+msgid "If checked, the credentials used to connect will be emtpy"
+msgstr ""
+"Si coché, les informations d'identification utilisées pour se connecter sera "
+"vide"
+
+#: transports/NX/NXTransport.py:61 transports/RDP/RDPTransport.py:37
+#: transports/RDP/TSRDPTransport.py:41
+#: transports/RGS-enterprise/RGSTransport.py:42
+#: transports/RGS-enterprise/TRGSTransport.py:47
+#: transports/TSNX/TSNXTransport.py:66
+msgid "If not empty, this username will be always used as credential"
+msgstr ""
+"Si ce n'est vide, ce nom d'utilisateur sera toujours utilisé comme des "
+"titres de compétences"
+
+#: transports/NX/NXTransport.py:62 transports/RDP/RDPTransport.py:38
+#: transports/RDP/TSRDPTransport.py:42
+#: transports/RGS-enterprise/RGSTransport.py:43
+#: transports/RGS-enterprise/TRGSTransport.py:48
+#: transports/TSNX/TSNXTransport.py:67
+msgid "If not empty, this password will be always used as credential"
+msgstr ""
+"Si ce n'est vide, ce mot de passe sera toujours utilisé comme des titres de "
+"compétences"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listen port"
+msgstr "Écouter le port"
+
+#: transports/NX/NXTransport.py:63 transports/TSNX/TSNXTransport.py:68
+msgid "Listening port of NX (ssh) at client machine"
+msgstr "Écoute le port de NX (ssh) à la machine client"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection"
+msgstr "Connexion"
+
+#: transports/NX/NXTransport.py:64 transports/TSNX/TSNXTransport.py:69
+msgid "Connection speed for this transport (quality)"
+msgstr "Vitesse de connexion pour ce transport (qualité)"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Session"
+msgstr "Session"
+
+#: transports/NX/NXTransport.py:71 transports/TSNX/TSNXTransport.py:76
+msgid "Desktop session"
+msgstr "Session de bureau"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Disk Cache"
+msgstr "Cache disque"
+
+#: transports/NX/NXTransport.py:76 transports/TSNX/TSNXTransport.py:81
+msgid "Cache size en Mb stored at disk"
+msgstr "Cache en taille que Mo stocké à disque"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Memory Cache"
+msgstr "Mémoire Cache"
+
+#: transports/NX/NXTransport.py:84 transports/TSNX/TSNXTransport.py:89
+msgid "Cache size en Mb keept at memory"
+msgstr "Taille en Mb montagne à la mémoire de cache."
+
+#: transports/NX/__init__.py:40 transports/TSNX/__init__.py:40
+msgid "NX Protocol"
+msgstr "Protocole de NX"
+
+#: transports/NX/__init__.py:46
+msgid "UDS Actor connector for NX (requires nomachine packages)"
+msgstr "Connecteur UDS acteur pour NX (nécessite nomachine packages)"
+
+#: transports/NX/web.py:74
+msgid ""
+"In order to use this transport, you need to install first Nomachine Nx "
+"Client version 3.5.x"
+msgstr ""
+"Afin d'utiliser ce transport, vous devez installer le premier Nomachine Nx "
+"Client version 3.5.x"
+
+#: transports/NX/web.py:75
+msgid "you can obtain it for your platform from"
+msgstr "vous pouvez l'obtenir pour votre plate-forme de"
+
+#: transports/NX/web.py:75
+msgid "nochamine web site"
+msgstr "site web de nochamine"
+
+#: transports/RDP/RDPTransport.py:30
+msgid "RDP Transport (direct)"
+msgstr "Transport de RDP (direct)"
+
+#: transports/RDP/RDPTransport.py:32
+msgid "RDP Transport for direct connection"
+msgstr "Transport de RDP pour une connexion directe"
+
+#: transports/RDP/RDPTransport.py:39 transports/RDP/TSRDPTransport.py:43
+#: transports/RGS-enterprise/RGSTransport.py:44
+#: transports/RGS-enterprise/TRGSTransport.py:49
+msgid ""
+"If not empty, this domain will be always used as credential (used as DOMAIN"
+"\\user)"
+msgstr ""
+"Si ce n'est vide, ce domaine sera toujours utilisé comme des titres de "
+"compétences (utilisé comme domaine\\User)"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "Allow Smartcards"
+msgstr "Permettre aux cartes à puce"
+
+#: transports/RDP/RDPTransport.py:40 transports/RDP/TSRDPTransport.py:44
+msgid "If checked, this transport will allow the use of smartcards"
+msgstr "Si cochée, ce transport permettra l'utilisation de cartes à puce"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "Allow Printers"
+msgstr "Permettre aux imprimantes"
+
+#: transports/RDP/RDPTransport.py:41 transports/RDP/TSRDPTransport.py:45
+msgid "If checked, this transport will allow the use of user printers"
+msgstr ""
+"Si cochée, ce transport permettra l'utilisation des imprimantes utilisateur"
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "Allow Drives"
+msgstr "Permettre aux lecteurs"
+
+#: transports/RDP/RDPTransport.py:42 transports/RDP/TSRDPTransport.py:46
+msgid "If checked, this transport will allow the use of user drives"
+msgstr ""
+"Si cochée, ce transport permettra l'utilisation des lecteurs de l'utilisateur"
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "Allow Serials"
+msgstr "Permettre aux publications en série"
+
+#: transports/RDP/RDPTransport.py:43 transports/RDP/TSRDPTransport.py:47
+msgid "If checked, this transport will allow the use of user serial ports"
+msgstr ""
+"Si cochée, ce transport permettra l'utilisation de l'utilisateur ports série"
+
+#: transports/RDP/TSRDPTransport.py:31
+msgid "RDP Transport (tunneled)"
+msgstr "Transport de RDP (tunnel)"
+
+#: transports/RDP/TSRDPTransport.py:33
+msgid "RDP Transport for tunneled connection"
+msgstr "Transport de RDP de connexion tunnelée"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid "Tunnel server"
+msgstr "Serveur de tunnel"
+
+#: transports/RDP/TSRDPTransport.py:37
+#: transports/RGS-enterprise/TRGSTransport.py:43
+#: transports/TSNX/TSNXTransport.py:62
+msgid ""
+"IP or Hostname of tunnel server send to client device (\"public\" ip) and "
+"port. (use HOST:PORT format)"
+msgstr ""
+"IP ou nom d'hôte du serveur de tunnel envoyer à la machine cliente (« public "
+"» ip) et port. (utilisez le format de l'hôte : PORT)"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid "Tunnel host check"
+msgstr "Tunnel hôte cocher"
+
+#: transports/RDP/TSRDPTransport.py:38
+#: transports/RGS-enterprise/TRGSTransport.py:44
+#: transports/TSNX/TSNXTransport.py:63
+msgid ""
+"If not empty, this server will be used to check if service is running before "
+"assigning it to user. (use HOST:PORT format)"
+msgstr ""
+"Si ce n'est vide, ce serveur sera utilisé pour vérifier si le service "
+"s'exécute avant assignant à l'utilisateur. (utilisez le format de l'hôte : "
+"PORT)"
+
+#: transports/RDP/__init__.py:17
+msgid "Remote Desktop Protocol"
+msgstr "Protocole Bureau distant"
+
+#: transports/RDP/web.py:83
+msgid "In order to use this service, you should first install CoRD."
+msgstr "Afin d'utiliser ce service, vous devez d'abord installer cordon."
+
+#: transports/RDP/web.py:84 transports/RGS-enterprise/web.py:82
+msgid "You can obtain it from"
+msgstr "Vous pouvez l'obtenir de"
+
+#: transports/RDP/web.py:84
+msgid "CoRD Website"
+msgstr "Site Web du cordon"
+
+#: transports/RGS-enterprise/RGSTransport.py:34
+#, fuzzy
+msgid "RGS Transport (direct)"
+msgstr "Transport de RDP (direct)"
+
+#: transports/RGS-enterprise/RGSTransport.py:36
+#, fuzzy
+msgid "RGS Transport for direct connection"
+msgstr "Transport de RDP pour une connexion directe"
+
+#: transports/RGS-enterprise/RGSTransport.py:45
+#: transports/RGS-enterprise/TRGSTransport.py:50
+msgid "Image quality"
+msgstr "Qualité de l'image"
+
+#: transports/RGS-enterprise/RGSTransport.py:46
+#: transports/RGS-enterprise/TRGSTransport.py:51
+msgid "Quality of image codec (0-100)"
+msgstr "Qualité du codec d'image (0-100)"
+
+#: transports/RGS-enterprise/RGSTransport.py:47
+#: transports/RGS-enterprise/TRGSTransport.py:52
+msgid "Adjustable Quality"
+msgstr "Qualité réglable"
+
+#: transports/RGS-enterprise/RGSTransport.py:48
+#: transports/RGS-enterprise/TRGSTransport.py:53
+msgid "If checked, the image quality will be adjustable with bandwidth"
+msgstr "Si cochée, la qualité de l'image sera réglable avec bande passante"
+
+#: transports/RGS-enterprise/RGSTransport.py:49
+#: transports/RGS-enterprise/TRGSTransport.py:54
+msgid "Min. Adjustable Quality"
+msgstr "Min. qualité réglable"
+
+#: transports/RGS-enterprise/RGSTransport.py:50
+#: transports/RGS-enterprise/TRGSTransport.py:55
+msgid ""
+"The lowest image quality applied to images to maintain the minimum update "
+"rate."
+msgstr ""
+"La qualité de l'image plus bas appliquée aux images afin de maintenir la "
+"mise à jour minimum taux."
+
+#: transports/RGS-enterprise/RGSTransport.py:51
+#: transports/RGS-enterprise/TRGSTransport.py:56
+msgid "Adjustable Frame Rate"
+msgstr "Cadence réglable"
+
+#: transports/RGS-enterprise/RGSTransport.py:52
+#: transports/RGS-enterprise/TRGSTransport.py:57
+msgid "Update rate threshold to begin adjusting image quality"
+msgstr ""
+"Seuil de vitesse de mise à jour pour commencer à ajuster la qualité de "
+"l'image"
+
+#: transports/RGS-enterprise/RGSTransport.py:53
+#: transports/RGS-enterprise/TRGSTransport.py:58
+msgid "Match Local Resolution"
+msgstr "Résolution locale de match"
+
+#: transports/RGS-enterprise/RGSTransport.py:54
+#: transports/RGS-enterprise/TRGSTransport.py:59
+msgid ""
+"Change the Sender's resolution to match the Receiver's resolution when "
+"connecting"
+msgstr ""
+"Modifier la résolution de l'expéditeur pour faire correspondre la résolution "
+"du séquestre lors de la connexion"
+
+#: transports/RGS-enterprise/RGSTransport.py:55
+#: transports/RGS-enterprise/TRGSTransport.py:60
+msgid "Redirect USB"
+msgstr "Redirection USB"
+
+#: transports/RGS-enterprise/RGSTransport.py:56
+#: transports/RGS-enterprise/TRGSTransport.py:61
+msgid "If checked, the USB will be redirected."
+msgstr "Si cochée, la clé USB est redirigée."
+
+#: transports/RGS-enterprise/RGSTransport.py:57
+#: transports/RGS-enterprise/TRGSTransport.py:62
+msgid "Redirect Audio"
+msgstr "Redirection Audio"
+
+#: transports/RGS-enterprise/RGSTransport.py:58
+#: transports/RGS-enterprise/TRGSTransport.py:63
+#, fuzzy
+msgid "If checked, the Audio will be redirected."
+msgstr ""
+"Si coché, les informations d'identification utilisées pour se connecter sera "
+"vide"
+
+#: transports/RGS-enterprise/RGSTransport.py:59
+#: transports/RGS-enterprise/TRGSTransport.py:64
+msgid "Redirect Mic"
+msgstr "Redirection Mic"
+
+#: transports/RGS-enterprise/RGSTransport.py:60
+#: transports/RGS-enterprise/TRGSTransport.py:65
+#, fuzzy
+msgid "If checked, the Mic will be redirected."
+msgstr ""
+"Si coché, les informations d'identification utilisées pour se connecter sera "
+"vide"
+
+#: transports/RGS-enterprise/TRGSTransport.py:36
+#, fuzzy
+msgid "RGS Transport (tunneled)"
+msgstr "Transport de RDP (tunnel)"
+
+#: transports/RGS-enterprise/TRGSTransport.py:38
+#, fuzzy
+msgid "RGS Transport for tunneled connection"
+msgstr "Transport de RDP de connexion tunnelée"
+
+#: transports/RGS-enterprise/web.py:81
+#, fuzzy
+msgid "In order to use this service, you should first install RGS Receiver."
+msgstr "Afin d'utiliser ce service, vous devez d'abord installer cordon."
+
+#: transports/RGS-enterprise/web.py:82
+#, fuzzy
+msgid "HP Website"
+msgstr "Site Web du cordon"
+
+#: transports/TSNX/TSNXTransport.py:55
+msgid "NX Transport (tunneled)"
+msgstr "Transport NX (tunnel)"
+
+#: transports/TSNX/TSNXTransport.py:57
+msgid "NX Transport for tunneled connection"
+msgstr "Transport NX pour connexion tunnelée"
+
+#: web/errors.py:58
+msgid "Unknown error"
+msgstr "Erreur inconnue"
+
+#: web/errors.py:59
+msgid "Transport not found"
+msgstr "Transport introuvable"
+
+#: web/errors.py:60
+msgid "Service not found"
+msgstr "Service introuvable"
+
+#: web/errors.py:61 xmlrpc/auths/AdminAuth.py:171
+msgid "Access denied"
+msgstr "Accès refusé"
+
+#: web/errors.py:62
+msgid ""
+"Invalid service. The service is not available at this moment. Please, try "
+"later"
+msgstr ""
+"Service non valide. Le service n'est pas disponible en ce moment. SVP, "
+"essayez plus tard"
+
+#: web/errors.py:63
+msgid "Maximum services limit reached. Please, contact administrator"
+msgstr ""
+"Limite de services maximale atteinte. Veuillez contacter administrateur"
+
+#: web/errors.py:64
+msgid "You need to enable cookies to let this application work"
+msgstr "Vous devez activer les cookies de laisser cette application"
+
+#: web/errors.py:65
+msgid "User service not found"
+msgstr "Service utilisateur introuvable"
+
+#: web/errors.py:66
+#, fuzzy
+msgid "Authenticator not found"
+msgstr "Authentificateur"
+
+#: web/errors.py:67 xmlrpc/auths/AdminAuth.py:169
+msgid "Invalid authenticator"
+msgstr "Authentificateur non valide"
+
+#: web/views.py:334
+#, fuzzy
+msgid "Authenticator do not provides information"
+msgstr "Authentificateur n'existe pas"
+
+#: web/forms/LoginForm.py:59
+msgid "Authenticator"
+msgstr "Authentificateur"
+
+#: xmlrpc/auths/AdminAuth.py:113
+msgid "Credentials no longer valid"
+msgstr "Informations d'identification n'est plus valides"
+
+#: xmlrpc/auths/AdminAuth.py:149
+msgid "Administration"
+msgstr "Administration"
+
+#: xmlrpc/auths/AdminAuth.py:164
+msgid "Invalid credentials"
+msgstr "Informations d'identification non valides"
+
+#: xmlrpc/auths/Authenticators.py:106
+msgid "Authenticator does not exists"
+msgstr "Authentificateur n'existe pas"
+
+#: xmlrpc/auths/Authenticators.py:161 xmlrpc/osmanagers/OSManagers.py:115
+#: xmlrpc/services/ServiceProviders.py:115
+#: xmlrpc/services/ServiceProviders.py:139 xmlrpc/services/Services.py:160
+#: xmlrpc/services/Services.py:184 xmlrpc/transports/Networks.py:86
+#: xmlrpc/transports/Networks.py:97
+#, python-format
+msgid "Name %s already exists"
+msgstr "Nom %s existe déjà"
+
+#: xmlrpc/auths/Authenticators.py:234
+msgid "Authenticator do not supports search"
+msgstr "Authenticateur ne soutient la recherche"
+
+#: xmlrpc/auths/Authenticators.py:240
+msgid "Specified authenticator do not exists anymore. Please, reload gui"
+msgstr "Authentificateur spécifiée n'existe pas plus. Veuillez recharger gui"
+
+#: xmlrpc/auths/Authenticators.py:244
+msgid "BUG: Reached a point that should never have been reached!!!"
+msgstr "BOGUE : Atteint un point qui devrait jamais été atteint!!!"
+
+#: xmlrpc/osmanagers/OSManagers.py:129
+msgid "This os mnager is being used by deployed services"
+msgstr ""
+"Ce gestionnaire de système d'exploitation est utilisé par les services de "
+"déploiements"
+
+#: xmlrpc/osmanagers/OSManagers.py:145
+msgid "There is deployed services using this os manager"
+msgstr ""
+"Il y a des services déployées à l'aide de ce gestionnaire de système "
+"d'exploitation"
+
+#: xmlrpc/osmanagers/OSManagers.py:147
+msgid "Can't find os manager"
+msgstr "Impossible de trouver os gestionnaire"
+
+#: xmlrpc/services/DeployedServices.py:52
+msgid "Unknown"
+msgstr "Inconnu"
+
+#: xmlrpc/services/DeployedServices.py:112
+#: xmlrpc/services/DeployedServices.py:176
+#: xmlrpc/services/DeployedServices.py:195
+#: xmlrpc/services/DeployedServices.py:211
+#: xmlrpc/services/DeployedServices.py:225
+#: xmlrpc/services/DeployedServices.py:252
+#: xmlrpc/services/DeployedServices.py:266
+msgid "Deployed Service does not exists"
+msgstr "Service déployée n'existe pas"
+
+#: xmlrpc/services/DeployedServices.py:209
+msgid "Group does not exists"
+msgstr "Groupe n'existe pas"
+
+#: xmlrpc/services/DeployedServices.py:237
+msgid "Can't find deployed service"
+msgstr "Impossible de trouver le service déployé"
+
+#: xmlrpc/services/DeployedServices.py:250
+msgid "Transport does not exists"
+msgstr "Transport n'existe pas"
+
+#: xmlrpc/services/DeployedServices.py:281
+msgid "Deployed service does not exists"
+msgstr "Service déployée n'existe pas"
+
+#: xmlrpc/services/ServiceProviders.py:154
+msgid "Can't delete service provider with services associated"
+msgstr ""
+"Impossible de supprimer le fournisseur de service avec services associés"
+
+#: xmlrpc/services/ServiceProviders.py:157
+msgid "Can't locate the service provider"
+msgstr "Impossible de trouver le fournisseur de services"
+
+#: xmlrpc/services/ServiceProviders.py:157 xmlrpc/services/Services.py:202
+#: xmlrpc/transports/Networks.py:70 xmlrpc/transports/Networks.py:78
+#: xmlrpc/transports/Networks.py:95
+msgid "Please, refresh interface"
+msgstr "Veuillez actualiser interface"
+
+#: xmlrpc/services/Services.py:199
+msgid "Can't delete services with deployed services associated"
+msgstr "Impossible de supprimer les services avec des services déployés liés"
+
+#: xmlrpc/services/Services.py:202
+msgid "Can't locate the service"
+msgstr "Impossible de localiser le service"
+
+#: xmlrpc/services/UserDeployedServices.py:94
+#: xmlrpc/services/UserDeployedServices.py:110
+msgid "The deployed service is not active"
+msgstr "Le service de déploiement n'est pas actif"
+
+#: xmlrpc/services/UserDeployedServices.py:97
+#: xmlrpc/services/UserDeployedServices.py:113
+msgid "This service don't allows assignations"
+msgstr "Ce service ne permet l'assignation"
+
+#: xmlrpc/services/UserDeployedServices.py:102
+#: xmlrpc/services/UserDeployedServices.py:120
+msgid "Deployed service not found!!! (refresh interface)"
+msgstr "Déployées service introuvable!!! (actualisation interface)"
+
+#: xmlrpc/services/UserDeployedServices.py:122
+msgid "User not found!!! (refresh interface)"
+msgstr "Utilisateur introuvable!!! (actualisation interface)"
+
+#: xmlrpc/services/UserDeployedServices.py:141
+msgid "No error"
+msgstr "Pas d'erreur"
+
+#: xmlrpc/services/UserDeployedServices.py:147
+msgid "User deployed service not found!!!"
+msgstr "Service de l'utilisateur ne se trouve pas déployée!!!"
+
+#: xmlrpc/transports/Networks.py:70
+msgid "Can't locate the transport"
+msgstr "Impossible de localiser le transport"
+
+#: xmlrpc/transports/Networks.py:78 xmlrpc/transports/Networks.py:95
+msgid "Can't locate the network"
+msgstr "Impossible de localiser le réseau"
+
+#~ msgid "None OS Manager"
+#~ msgstr "Aucun gestionnaire de l'OS"
+
+#~ msgid "Os Manager with no actions"
+#~ msgstr "Gestionnaire d'os avec aucune action"
+
+#~ msgid "Base Service"
+#~ msgstr "Service de base"
+
+#~ msgid "None"
+#~ msgstr "Aucun"
diff --git a/trunk/server/src/uds/management/__init__.py b/trunk/server/src/uds/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/management/commands/__init__.py b/trunk/server/src/uds/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/management/commands/config.py b/trunk/server/src/uds/management/commands/config.py
new file mode 100644
index 00000000..69104596
--- /dev/null
+++ b/trunk/server/src/uds/management/commands/config.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.core.management.base import BaseCommand
+from optparse import make_option
+from uds.core.util.Config import Config, GLOBAL_SECTION
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Command(BaseCommand):
+ args = ""
+ help = "Updates configuration values. If mod is omitted, UDS will be used. Omit whitespaces betwen name, =, and value (they must be a single param)"
+
+ def handle(self, *args, **options):
+ logger.debug("Handling settings")
+ try:
+ for config in args:
+ print config
+ first, value = config.split('=')
+ first = first.split('.')
+ if len(first) == 2:
+ mod, name = first
+ else:
+ mod, name = GLOBAL_SECTION, first[0]
+ Config.update(mod, name, value)
+ except Exception:
+ logger.exception("Error")
+
diff --git a/trunk/server/src/uds/management/commands/taskManager.py b/trunk/server/src/uds/management/commands/taskManager.py
new file mode 100644
index 00000000..a04f3889
--- /dev/null
+++ b/trunk/server/src/uds/management/commands/taskManager.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from server.settings import SITE_ROOT, LOGDIR
+from django.utils.daemonize import become_daemon
+from uds.core.managers.TaskManager import TaskManager
+import logging, sys, os, signal, time
+
+logger = logging.getLogger(__name__)
+
+PID_FILE = 'taskmanager.pid'
+
+def getPidFile():
+ return SITE_ROOT + '/' + PID_FILE
+
+class Command(BaseCommand):
+ args = "None"
+ help = "Executes the task manager as a daemon. No parameter show current status of task manager"
+
+ option_list = BaseCommand.option_list + (
+ make_option('--start',
+ action='store_true',
+ dest='start',
+ default=False,
+ help='Starts a new daemon'),
+ make_option('--stop',
+ action='store_true',
+ dest='stop',
+ default=False,
+ help='Stop any running daemon'),
+ )
+
+ def handle(self, *args, **options):
+ logger.info("Running task manager command")
+
+ start = options['start'] and True or False
+ stop = options['stop'] and True or False
+
+ pid = None
+ try:
+ pid = int(file(getPidFile(), 'r').readline())
+ except Exception:
+ pid = None
+
+ if stop is True and pid is not None:
+ try:
+ logger.info('Stopping task manager. pid: {0}'.format(pid))
+ os.kill( pid, signal.SIGTERM )
+ time.sleep(1) # Wait a bit before running new one
+ os.unlink(getPidFile())
+ except Exception:
+ logger.error("Could not stop task manager (maybe it's not runing?)")
+ os.unlink(getPidFile())
+
+ if start is True:
+ logger.info('Starting task manager.')
+ become_daemon(SITE_ROOT, LOGDIR + '/taskManagerStdout.log', LOGDIR + '/taskManagerStderr.log')
+ pid = str(os.getpid())
+ file(getPidFile() ,'w+').write("%s\n" % pid)
+
+ manager = TaskManager()
+ manager.run()
+
+ if start is False and stop is False:
+ if pid is not None:
+ sys.stdout.write("Task manager found running (pid file exists: {0})\n".format(pid))
+ else:
+ sys.stdout.write("Task manager not foud (pid file do not exits)\n")
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/migrations/0001_initial.py b/trunk/server/src/uds/migrations/0001_initial.py
new file mode 100644
index 00000000..8437c4eb
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0001_initial.py
@@ -0,0 +1,537 @@
+# encoding: utf-8
+#@PydevCodeAnalysisIgnore
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Provider'
+ db.create_table('uds_provider', (
+ ('comments', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('data_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
+ ))
+ db.send_create_signal('uds', ['Provider'])
+
+ # Adding model 'Service'
+ db.create_table('uds_service', (
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('data_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('comments', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('provider', self.gf('django.db.models.fields.related.ForeignKey')(related_name='services', to=orm['uds.Provider'])),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['Service'])
+
+ # Adding unique constraint on 'Service', fields ['provider', 'name']
+ db.create_unique('uds_service', ['provider_id', 'name'])
+
+ # Adding model 'OSManager'
+ db.create_table('uds_osmanager', (
+ ('comments', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('data_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
+ ))
+ db.send_create_signal('uds', ['OSManager'])
+
+ # Adding model 'Transport'
+ db.create_table('uds_transport', (
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
+ ('data_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('comments', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('priority', self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True)),
+ ('nets_positive', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['Transport'])
+
+ # Adding model 'Authenticator'
+ db.create_table('uds_authenticator', (
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)),
+ ('data_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('comments', self.gf('django.db.models.fields.TextField')(default='')),
+ ('priority', self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['Authenticator'])
+
+ # Adding model 'User'
+ db.create_table('uds_user', (
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('staff_member', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('manager', self.gf('django.db.models.fields.related.ForeignKey')(related_name='users', to=orm['uds.Authenticator'])),
+ ('comments', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('real_name', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('state', self.gf('django.db.models.fields.CharField')(max_length=1, db_index=True)),
+ ('is_admin', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('last_access', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1972, 7, 1, 0, 0))),
+ ('password', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['User'])
+
+ # Adding unique constraint on 'User', fields ['manager', 'name']
+ db.create_unique('uds_user', ['manager_id', 'name'])
+
+ # Adding model 'Group'
+ db.create_table('uds_group', (
+ ('manager', self.gf('django.db.models.fields.related.ForeignKey')(related_name='groups', to=orm['uds.Authenticator'])),
+ ('state', self.gf('django.db.models.fields.CharField')(default='A', max_length=1, db_index=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('comments', self.gf('django.db.models.fields.CharField')(default='', max_length=256)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ))
+ db.send_create_signal('uds', ['Group'])
+
+ # Adding unique constraint on 'Group', fields ['manager', 'name']
+ db.create_unique('uds_group', ['manager_id', 'name'])
+
+ # Adding M2M table for field users on 'Group'
+ db.create_table('uds_group_users', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('group', models.ForeignKey(orm['uds.group'], null=False)),
+ ('user', models.ForeignKey(orm['uds.user'], null=False))
+ ))
+ db.create_unique('uds_group_users', ['group_id', 'user_id'])
+
+ # Adding model 'UserPreference'
+ db.create_table('uds_userpreference', (
+ ('value', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='preferences', to=orm['uds.User'])),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('module', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)),
+ ))
+ db.send_create_signal('uds', ['UserPreference'])
+
+ # Adding unique constraint on 'UserPreference', fields ['module', 'name']
+ db.create_unique('uds_userpreference', ['module', 'name'])
+
+ # Adding model 'DeployedService'
+ db.create_table('uds__deployed_service', (
+ ('initial_srvs', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
+ ('osmanager', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='deployedServices', null=True, to=orm['uds.OSManager'])),
+ ('name', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
+ ('service', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='deployedServices', null=True, to=orm['uds.Service'])),
+ ('current_pub_revision', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)),
+ ('max_srvs', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
+ ('comments', self.gf('django.db.models.fields.CharField')(default='', max_length=256)),
+ ('authenticator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='deployedServices', null=True, to=orm['uds.Authenticator'])),
+ ('state', self.gf('django.db.models.fields.CharField')(default='A', max_length=1, db_index=True)),
+ ('cache_l2_srvs', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
+ ('cache_l1_srvs', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['DeployedService'])
+
+ # Adding M2M table for field transports on 'DeployedService'
+ db.create_table('uds__ds_trans', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('deployedservice', models.ForeignKey(orm['uds.deployedservice'], null=False)),
+ ('transport', models.ForeignKey(orm['uds.transport'], null=False))
+ ))
+ db.create_unique('uds__ds_trans', ['deployedservice_id', 'transport_id'])
+
+ # Adding M2M table for field assignedGroups on 'DeployedService'
+ db.create_table('uds__ds_grps', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('deployedservice', models.ForeignKey(orm['uds.deployedservice'], null=False)),
+ ('group', models.ForeignKey(orm['uds.group'], null=False))
+ ))
+ db.create_unique('uds__ds_grps', ['deployedservice_id', 'group_id'])
+
+ # Adding model 'DeployedServicePublication'
+ db.create_table('uds__deployed_service_pub', (
+ ('deployed_service', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publications', to=orm['uds.DeployedService'])),
+ ('state', self.gf('django.db.models.fields.CharField')(default='P', max_length=1, db_index=True)),
+ ('state_date', self.gf('django.db.models.fields.DateTimeField')()),
+ ('publish_date', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('revision', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)),
+ ))
+ db.send_create_signal('uds', ['DeployedServicePublication'])
+
+ # Adding model 'UserService'
+ db.create_table('uds__user_service', (
+ ('cache_level', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0, db_index=True)),
+ ('publication', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='userServices', null=True, to=orm['uds.DeployedServicePublication'])),
+ ('in_use', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('os_state', self.gf('django.db.models.fields.CharField')(default='P', max_length=1)),
+ ('friendly_name', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
+ ('deployed_service', self.gf('django.db.models.fields.related.ForeignKey')(related_name='userServices', to=orm['uds.DeployedService'])),
+ ('creation_date', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.CharField')(default='P', max_length=1, db_index=True)),
+ ('state_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='userServices', null=True, blank=True, to=orm['uds.User'])),
+ ('in_use_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1972, 7, 1, 0, 0))),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('unique_id', self.gf('django.db.models.fields.CharField')(default='', max_length=128, db_index=True)),
+ ))
+ db.send_create_signal('uds', ['UserService'])
+
+ # Adding model 'Cache'
+ db.create_table('uds_utility_cache', (
+ ('owner', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('validity', self.gf('django.db.models.fields.IntegerField')(default=60)),
+ ('value', self.gf('django.db.models.fields.TextField')(default='')),
+ ('key', self.gf('django.db.models.fields.CharField')(max_length=64, primary_key=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')()),
+ ))
+ db.send_create_signal('uds', ['Cache'])
+
+ # Adding model 'Config'
+ db.create_table('uds_configuration', (
+ ('value', self.gf('django.db.models.fields.TextField')(default='')),
+ ('section', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('key', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ))
+ db.send_create_signal('uds', ['Config'])
+
+ # Adding unique constraint on 'Config', fields ['section', 'key']
+ db.create_unique('uds_configuration', ['section', 'key'])
+
+ # Adding model 'Storage'
+ db.create_table('uds_storage', (
+ ('owner', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('data', self.gf('django.db.models.fields.CharField')(default='', max_length=1024)),
+ ('key', self.gf('django.db.models.fields.CharField')(max_length=64, primary_key=True)),
+ ('attr1', self.gf('django.db.models.fields.CharField')(default=None, max_length=64, null=True, db_index=True, blank=True)),
+ ))
+ db.send_create_signal('uds', ['Storage'])
+
+ # Adding model 'UniqueId'
+ db.create_table('uds_uniqueid', (
+ ('owner', self.gf('django.db.models.fields.CharField')(default='', max_length=128, db_index=True)),
+ ('assigned', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True, blank=True)),
+ ('basename', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('seq', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ))
+ db.send_create_signal('uds', ['UniqueId'])
+
+ # Adding unique constraint on 'UniqueId', fields ['basename', 'seq']
+ db.create_unique('uds_uniqueid', ['basename', 'seq'])
+
+ # Adding model 'Scheduler'
+ db.create_table('uds_scheduler', (
+ ('last_execution', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('frecuency', self.gf('django.db.models.fields.PositiveIntegerField')(default=86400)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('state', self.gf('django.db.models.fields.CharField')(default='X', max_length=1, db_index=True)),
+ ('next_execution', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1972, 7, 1, 0, 0), db_index=True)),
+ ('owner_server', self.gf('django.db.models.fields.CharField')(default='', max_length=64, db_index=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
+ ))
+ db.send_create_signal('uds', ['Scheduler'])
+
+ # Adding model 'DelayedTask'
+ db.create_table('uds_delayedtask', (
+ ('insert_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('execution_delay', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ('execution_time', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
+ ('instance', self.gf('django.db.models.fields.TextField')()),
+ ('tag', self.gf('django.db.models.fields.CharField')(max_length=64, db_index=True)),
+ ('type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('uds', ['DelayedTask'])
+
+ # Adding model 'Network'
+ db.create_table('uds_network', (
+ ('net_start', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('net_end', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
+ ))
+ db.send_create_signal('uds', ['Network'])
+
+ # Adding M2M table for field transports on 'Network'
+ db.create_table('uds_net_trans', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('network', models.ForeignKey(orm['uds.network'], null=False)),
+ ('transport', models.ForeignKey(orm['uds.transport'], null=False))
+ ))
+ db.create_unique('uds_net_trans', ['network_id', 'transport_id'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Provider'
+ db.delete_table('uds_provider')
+
+ # Deleting model 'Service'
+ db.delete_table('uds_service')
+
+ # Removing unique constraint on 'Service', fields ['provider', 'name']
+ db.delete_unique('uds_service', ['provider_id', 'name'])
+
+ # Deleting model 'OSManager'
+ db.delete_table('uds_osmanager')
+
+ # Deleting model 'Transport'
+ db.delete_table('uds_transport')
+
+ # Deleting model 'Authenticator'
+ db.delete_table('uds_authenticator')
+
+ # Deleting model 'User'
+ db.delete_table('uds_user')
+
+ # Removing unique constraint on 'User', fields ['manager', 'name']
+ db.delete_unique('uds_user', ['manager_id', 'name'])
+
+ # Deleting model 'Group'
+ db.delete_table('uds_group')
+
+ # Removing unique constraint on 'Group', fields ['manager', 'name']
+ db.delete_unique('uds_group', ['manager_id', 'name'])
+
+ # Removing M2M table for field users on 'Group'
+ db.delete_table('uds_group_users')
+
+ # Deleting model 'UserPreference'
+ db.delete_table('uds_userpreference')
+
+ # Removing unique constraint on 'UserPreference', fields ['module', 'name']
+ db.delete_unique('uds_userpreference', ['module', 'name'])
+
+ # Deleting model 'DeployedService'
+ db.delete_table('uds__deployed_service')
+
+ # Removing M2M table for field transports on 'DeployedService'
+ db.delete_table('uds__ds_trans')
+
+ # Removing M2M table for field assignedGroups on 'DeployedService'
+ db.delete_table('uds__ds_grps')
+
+ # Deleting model 'DeployedServicePublication'
+ db.delete_table('uds__deployed_service_pub')
+
+ # Deleting model 'UserService'
+ db.delete_table('uds__user_service')
+
+ # Deleting model 'Cache'
+ db.delete_table('uds_utility_cache')
+
+ # Deleting model 'Config'
+ db.delete_table('uds_configuration')
+
+ # Removing unique constraint on 'Config', fields ['section', 'key']
+ db.delete_unique('uds_configuration', ['section', 'key'])
+
+ # Deleting model 'Storage'
+ db.delete_table('uds_storage')
+
+ # Deleting model 'UniqueId'
+ db.delete_table('uds_uniqueid')
+
+ # Removing unique constraint on 'UniqueId', fields ['basename', 'seq']
+ db.delete_unique('uds_uniqueid', ['basename', 'seq'])
+
+ # Deleting model 'Scheduler'
+ db.delete_table('uds_scheduler')
+
+ # Deleting model 'DelayedTask'
+ db.delete_table('uds_delayedtask')
+
+ # Deleting model 'Network'
+ db.delete_table('uds_network')
+
+ # Removing M2M table for field transports on 'Network'
+ db.delete_table('uds_net_trans')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'authenticator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Authenticator']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'unique_together': "(('module', 'name'),)", 'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0002_auto__del_unique_userpreference_name_module.py b/trunk/server/src/uds/migrations/0002_auto__del_unique_userpreference_name_module.py
new file mode 100644
index 00000000..6484d28b
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0002_auto__del_unique_userpreference_name_module.py
@@ -0,0 +1,200 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Removing unique constraint on 'UserPreference', fields ['name', 'module']
+ db.delete_unique('uds_userpreference', ['name', 'module'])
+
+
+ def backwards(self, orm):
+
+ # Adding unique constraint on 'UserPreference', fields ['name', 'module']
+ db.create_unique('uds_userpreference', ['name', 'module'])
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'authenticator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Authenticator']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0003_auto__del_field_deployedservice_authenticator.py b/trunk/server/src/uds/migrations/0003_auto__del_field_deployedservice_authenticator.py
new file mode 100644
index 00000000..cabe40d0
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0003_auto__del_field_deployedservice_authenticator.py
@@ -0,0 +1,199 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting field 'DeployedService.authenticator'
+ db.delete_column('uds__deployed_service', 'authenticator_id')
+
+
+ def backwards(self, orm):
+
+ # Adding field 'DeployedService.authenticator'
+ db.add_column('uds__deployed_service', 'authenticator', self.gf('django.db.models.fields.related.ForeignKey')(related_name='deployedServices', null=True, to=orm['uds.Authenticator'], blank=True), keep_default=False)
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0004_auto__add_field_deployedservice_state_date.py b/trunk/server/src/uds/migrations/0004_auto__add_field_deployedservice_state_date.py
new file mode 100644
index 00000000..385403c1
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0004_auto__add_field_deployedservice_state_date.py
@@ -0,0 +1,200 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'DeployedService.state_date'
+ db.add_column('uds__deployed_service', 'state_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(1972, 7, 1, 0, 0)), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'DeployedService.state_date'
+ db.delete_column('uds__deployed_service', 'state_date')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0005_auto__add_field_config_crypt.py b/trunk/server/src/uds/migrations/0005_auto__add_field_config_crypt.py
new file mode 100644
index 00000000..48312f69
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0005_auto__add_field_config_crypt.py
@@ -0,0 +1,201 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Config.crypt'
+ db.add_column('uds_configuration', 'crypt', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Config.crypt'
+ db.delete_column('uds_configuration', 'crypt')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0006_auto__chg_field_storage_data.py b/trunk/server/src/uds/migrations/0006_auto__chg_field_storage_data.py
new file mode 100644
index 00000000..05cf75e6
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0006_auto__chg_field_storage_data.py
@@ -0,0 +1,201 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Changing field 'Storage.data'
+ db.alter_column('uds_storage', 'data', self.gf('django.db.models.fields.TextField')())
+
+
+ def backwards(self, orm):
+
+ # Changing field 'Storage.data'
+ db.alter_column('uds_storage', 'data', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
diff --git a/trunk/server/src/uds/migrations/0007_auto__add_field_config_long.py b/trunk/server/src/uds/migrations/0007_auto__add_field_config_long.py
new file mode 100644
index 00000000..8950def6
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0007_auto__add_field_config_long.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Config.long'
+ db.add_column('uds_configuration', 'long',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Config.long'
+ db.delete_column('uds_configuration', 'long')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'long': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'ordering': "('publish_date',)", 'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'ordering': "('-seq',)", 'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'ordering': "('creation_date',)", 'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
\ No newline at end of file
diff --git a/trunk/server/src/uds/migrations/0008_auto__add_userservicelog__add_field_userservice_src_hostname__add_fiel.py b/trunk/server/src/uds/migrations/0008_auto__add_userservicelog__add_field_userservice_src_hostname__add_fiel.py
new file mode 100644
index 00000000..479fcd52
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0008_auto__add_userservicelog__add_field_userservice_src_hostname__add_fiel.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'UserServiceLog'
+ db.create_table('uds__us_log', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user_service', self.gf('django.db.models.fields.related.ForeignKey')(related_name='log', to=orm['uds.UserService'])),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)),
+ ('source', self.gf('django.db.models.fields.CharField')(default='internal', max_length=16, db_index=True)),
+ ('level', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0, db_index=True)),
+ ('data', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
+ ))
+ db.send_create_signal('uds', ['UserServiceLog'])
+
+ # Adding field 'UserService.src_hostname'
+ db.add_column('uds__user_service', 'src_hostname',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=64),
+ keep_default=False)
+
+ # Adding field 'UserService.src_ip'
+ db.add_column('uds__user_service', 'src_ip',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=15),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting model 'UserServiceLog'
+ db.delete_table('uds__us_log')
+
+ # Deleting field 'UserService.src_hostname'
+ db.delete_column('uds__user_service', 'src_hostname')
+
+ # Deleting field 'UserService.src_ip'
+ db.delete_column('uds__user_service', 'src_ip')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'long': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'ordering': "('publish_date',)", 'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'ordering': "('-seq',)", 'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'ordering': "('creation_date',)", 'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'src_hostname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
+ 'src_ip': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '15'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ },
+ 'uds.userservicelog': {
+ 'Meta': {'object_name': 'UserServiceLog', 'db_table': "'uds__us_log'"},
+ 'created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'source': ('django.db.models.fields.CharField', [], {'default': "'internal'", 'max_length': '16', 'db_index': 'True'}),
+ 'user_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['uds.UserService']"})
+ }
+ }
+
+ complete_apps = ['uds']
\ No newline at end of file
diff --git a/trunk/server/src/uds/migrations/0009_auto__del_userservicelog__add_log.py b/trunk/server/src/uds/migrations/0009_auto__del_userservicelog__add_log.py
new file mode 100644
index 00000000..ea5fd92c
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0009_auto__del_userservicelog__add_log.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Deleting model 'UserServiceLog'
+ db.rename_table('uds__us_log', 'uds_log')
+ db.rename_column('uds_log', 'user_service_id', 'owner_id')
+
+ db.send_create_signal('uds', ['Log'])
+
+
+ def backwards(self, orm):
+ # Adding model 'UserServiceLog'
+ db.rename_column('uds_log', 'owner_id', 'user_service_id')
+ db.rename_table('uds_log', 'uds__us_log')
+
+ db.send_create_signal('uds', ['UserServiceLog'])
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'long': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'ordering': "('publish_date',)", 'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.log': {
+ 'Meta': {'object_name': 'Log'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'source': ('django.db.models.fields.CharField', [], {'default': "'internal'", 'max_length': '16', 'db_index': 'True'}),
+ 'owner_id': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'ordering': "('-seq',)", 'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'ordering': "('creation_date',)", 'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'src_hostname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
+ 'src_ip': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '15'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
\ No newline at end of file
diff --git a/trunk/server/src/uds/migrations/0010_auto__add_field_log_owner_type.py b/trunk/server/src/uds/migrations/0010_auto__add_field_log_owner_type.py
new file mode 100644
index 00000000..935894b4
--- /dev/null
+++ b/trunk/server/src/uds/migrations/0010_auto__add_field_log_owner_type.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Log.owner_type'
+ db.add_column('uds_log', 'owner_type',
+ self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Log.owner_type'
+ db.delete_column('uds_log', 'owner_type')
+
+
+ models = {
+ 'uds.authenticator': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Authenticator'},
+ 'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.cache': {
+ 'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.config': {
+ 'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
+ 'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'long': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'uds.delayedtask': {
+ 'Meta': {'object_name': 'DelayedTask'},
+ 'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'instance': ('django.db.models.fields.TextField', [], {}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'uds.deployedservice': {
+ 'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
+ 'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
+ 'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
+ 'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.deployedservicepublication': {
+ 'Meta': {'ordering': "('publish_date',)", 'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'uds.group': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
+ 'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
+ },
+ 'uds.log': {
+ 'Meta': {'object_name': 'Log'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'owner_id': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'owner_type': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'source': ('django.db.models.fields.CharField', [], {'default': "'internal'", 'max_length': '16', 'db_index': 'True'})
+ },
+ 'uds.network': {
+ 'Meta': {'object_name': 'Network'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
+ },
+ 'uds.osmanager': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'OSManager'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.provider': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+ },
+ 'uds.scheduler': {
+ 'Meta': {'object_name': 'Scheduler'},
+ 'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
+ 'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.service': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
+ },
+ 'uds.storage': {
+ 'Meta': {'object_name': 'Storage'},
+ 'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.transport': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Transport'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+ 'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+ },
+ 'uds.uniqueid': {
+ 'Meta': {'ordering': "('-seq',)", 'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
+ 'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ 'uds.user': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
+ 'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
+ },
+ 'uds.userpreference': {
+ 'Meta': {'object_name': 'UserPreference'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'uds.userservice': {
+ 'Meta': {'ordering': "('creation_date',)", 'object_name': 'UserService', 'db_table': "'uds__user_service'"},
+ 'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
+ 'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
+ 'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
+ 'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
+ 'src_hostname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
+ 'src_ip': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '15'}),
+ 'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
+ 'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
+ }
+ }
+
+ complete_apps = ['uds']
\ No newline at end of file
diff --git a/trunk/server/src/uds/migrations/__init__.py b/trunk/server/src/uds/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/models.py b/trunk/server/src/uds/models.py
new file mode 100644
index 00000000..15f07c74
--- /dev/null
+++ b/trunk/server/src/uds/models.py
@@ -0,0 +1,1819 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.db import models
+from django.db.models import signals
+from uds.core.jobs.JobsFactory import JobsFactory
+from uds.core.Environment import Environment
+from uds.core.util.db.LockingManager import LockingManager
+from uds.core.util.State import State
+from uds.core.util import log
+from uds.core.services.Exceptions import InvalidServiceException
+from datetime import datetime, timedelta
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+NEVER = datetime(1972, 7, 1)
+
+
+def getSqlDatetime():
+ '''
+ Returns the current date/time of the database server.
+
+ We use this time as method of keeping all operations betwen different servers in sync.
+
+ We support get database datetime for:
+ * mysql
+ * sqlite
+ '''
+ from django.db import connection
+ con = connection
+ cursor = con.cursor()
+ if con.vendor == 'mysql':
+ cursor.execute('SELECT NOW()')
+ return cursor.fetchone()[0]
+ return datetime.now() # If not know how to get database datetime, returns local datetime (this is fine for sqlite, which is local)
+
+
+
+# Services
+class Provider(models.Model):
+ '''
+ A Provider represents the Service provider itself, (i.e. a KVM Server or a Terminal Server)
+ '''
+ name = models.CharField(max_length=128, unique = True)
+ data_type = models.CharField(max_length=128)
+ data = models.TextField(default='')
+ comments = models.CharField(max_length = 256)
+
+ class Meta:
+ '''
+ Meta class to declare default order
+ '''
+ ordering = ('name',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self, values = None):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ spType = self.getType()
+ env = self.getEnvironment()
+ sp = spType(env, values)
+
+ # Only unserializes if this is not initialized via user interface and
+ # data contains something
+ if values == None and self.data != None and self.data != '':
+ sp.unserialize(self.data)
+ return sp
+
+ def getType(self):
+ from uds.core import services
+ '''
+ Get the type of the object this record represents.
+
+ The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
+
+ Returns:
+ The python type for this record object
+ '''
+ return services.factory().lookup(self.data_type)
+
+ def __unicode__(self):
+ return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Provider class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ # Only tries to get instance if data is not empty
+ if toDelete.data != '':
+ s = toDelete.getInstance()
+ s.destroy()
+ s.env().clearRelatedData()
+
+ logger.debug('Before delete service provider '.format(toDelete))
+
+#: Connects a pre deletion signal to Provider
+signals.pre_delete.connect(Provider.beforeDelete, sender = Provider)
+
+class Service(models.Model):
+ '''
+ A Service represents an specidied type of service offered to final users, with it configuration (i.e. a KVM Base Machine for cloning
+ or a Terminal Server configuration).
+ '''
+ provider = models.ForeignKey(Provider, related_name='services')
+ name = models.CharField(max_length=128, unique = False)
+ data_type = models.CharField(max_length=128)
+ data = models.TextField(default='')
+ comments = models.CharField(max_length = 256)
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ ordering = ('name',)
+ unique_together = (("provider", "name"),)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self, values = None):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ prov = self.provider.getInstance()
+ sType = prov.getServiceByType(self.data_type)
+ env = self.getEnvironment()
+ s = sType(env, prov, values)
+ # Only unserializes if this is not initialized via user interface and
+ # data contains something
+ if values == None and self.data != None and self.data != '':
+ s.unserialize(self.data)
+ return s
+
+ def getType(self):
+ '''
+ Get the type of the object this record represents.
+
+ The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
+
+ Returns:
+ The python type for this record object
+
+ :note: We only need to get info from this, not access specific data (class specific info)
+ '''
+ return self.provider.getType().getServiceByType(self.data_type)
+
+ def __unicode__(self):
+ return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ # Only tries to get instance if data is not empty
+ if toDelete.data != '':
+ s = toDelete.getInstance()
+ s.destroy()
+ s.env().clearRelatedData()
+
+ logger.debug('Before delete service '.format(toDelete))
+
+#: Connects a pre deletion signal to Service
+signals.pre_delete.connect(Service.beforeDelete, sender = Service)
+
+
+class OSManager(models.Model):
+ '''
+ An OS Manager represents a manager for responding requests for agents inside services.
+ '''
+ name = models.CharField(max_length=128, unique = True)
+ data_type = models.CharField(max_length=128)
+ data = models.TextField(default='')
+ comments = models.CharField(max_length = 256)
+
+ class Meta:
+ '''
+ Meta class to declare default order
+ '''
+ ordering = ('name',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self, values = None):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ osType = self.getType()
+ env = self.getEnvironment()
+ os = osType(env, values)
+ # Only unserializes if this is not initialized via user interface and
+ # data contains something
+ if values == None and self.data != None and self.data != '':
+ os.unserialize(self.data)
+ return os
+
+ def getType(self):
+ '''
+ Get the type of the object this record represents.
+
+ The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
+
+ Returns:
+ The python type for this record object
+
+ :note: We only need to get info from this, not access specific data (class specific info)
+ '''
+ # We only need to get info from this, not access specific data (class specific info)
+ from uds.core import osmanagers
+ return osmanagers.factory().lookup(self.data_type)
+
+
+ def __unicode__(self):
+ return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
+ def remove(self):
+ '''
+ Removes this OS Manager only if there is no associated deployed service using it.
+
+ Returns:
+ True if the object has been removed
+
+ False if the object can't be removed because it is being used by some DeployedService
+
+ Raises:
+ '''
+ if self.deployedServices.all().count() > 0:
+ return False
+ self.delete()
+ return True
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ # Only tries to get instance if data is not empty
+ if toDelete.data != '':
+ s = toDelete.getInstance()
+ s.destroy()
+ s.env().clearRelatedData()
+
+ logger.debug('Before delete os manager '.format(toDelete))
+
+#: Connects a pre deletion signal to OS Manager
+signals.pre_delete.connect(OSManager.beforeDelete, sender = OSManager)
+
+class Transport(models.Model):
+ '''
+ A Transport represents a way of connecting the user with the service.
+
+ Sample of transports are RDP, Spice, Web file uploader, etc...
+ '''
+ name = models.CharField(max_length=128, unique = True)
+ data_type = models.CharField(max_length=128)
+ data = models.TextField(default='')
+ comments = models.CharField(max_length = 256)
+ priority = models.IntegerField(default=0, db_index=True)
+ nets_positive = models.BooleanField(default=False)
+
+ class Meta:
+ '''
+ Meta class to declare default order
+ '''
+ ordering = ('name',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self, values = None):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ tType = self.getType()
+ env = self.getEnvironment()
+ tr = tType(env, values)
+ # Only unserializes if this is not initialized via user interface and
+ # data contains something
+ if values == None and self.data != None and self.data != '':
+ tr.unserialize(self.data)
+ return tr
+
+ def getType(self):
+ '''
+ Get the type of the object this record represents.
+
+ The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
+
+ Returns:
+ The python type for this record object
+
+ :note: We only need to get info from this, not access specific data (class specific info)
+ '''
+ from uds.core import transports
+
+ return transports.factory().lookup(self.data_type)
+
+ def validForIp(self, ip):
+ '''
+ Checks if this transport is valid for the specified IP.
+
+ Args:
+ ip: Numeric ip address to check validity for. (xxx.xxx.xxx.xxx).
+
+ Returns:
+ True if the ip can access this Transport.
+
+ False if the ip can't access this Transport.
+
+ The ip check is done this way:
+ * If The associated network is empty, the result is always True
+ * If the associated network is not empty, and nets_positive (field) is True, the result will be True if
+ the ip is contained in any subnet associated with this transport.
+ * If the associated network is empty, and nets_positive (field) is False, the result will be True if
+ the ip is NOT contained in ANY subnet associated with this transport.
+
+ Raises:
+
+ :note: Ip addresses has been only tested with IPv4 addresses
+ '''
+ if self.networks.count() == 0:
+ return True
+ ip = Network.ipToLong(ip)
+ if self.nets_positive:
+ return self.networks.filter(net_start__lte=ip, net_end__gte=ip).count() > 0
+ else:
+ return self.networks.filter(net_start__lte=ip, net_end__gte=ip).count() == 0
+
+ def __unicode__(self):
+ return u"{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+
+ # Only tries to get instance if data is not empty
+ if toDelete.data != '':
+ s = toDelete.getInstance()
+ s.destroy()
+ s.env().clearRelatedData()
+
+ logger.debug('Before delete transport '.format(toDelete))
+
+#: Connects a pre deletion signal to OS Manager
+signals.pre_delete.connect(Transport.beforeDelete, sender = Transport)
+
+# Authenticators
+class Authenticator(models.Model):
+ '''
+ This class represents an Authenticator inside the platform.
+ Sample authenticators are LDAP, Active Directory, SAML, ...
+ '''
+ name = models.CharField(max_length=128, unique = True)
+ data_type = models.CharField(max_length=128)
+ data = models.TextField(default='')
+ comments = models.TextField(default='')
+ priority = models.IntegerField(default=0, db_index = True)
+
+ class Meta:
+ '''
+ Meta class to declare default order
+ '''
+ ordering = ('name',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self, values = None):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ auType = self.getType()
+ env = self.getEnvironment()
+ auth = auType(self, env, values)
+ # Only unserializes if this is not initialized via user interface and
+ # data contains something
+ if values == None and self.data != None and self.data != '':
+ auth.unserialize(self.data)
+ return auth
+
+ def getType(self):
+ '''
+ Get the type of the object this record represents.
+
+ The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
+
+ Returns:
+ The python type for this record object
+
+ :note: We only need to get info from this, not access specific data (class specific info)
+ '''
+ from uds.core import auths
+ return auths.factory().lookup(self.data_type)
+
+ def isOfType(self, type_):
+ return self.data_type == type_
+
+ def getOrCreateUser(self, username, realName = None):
+ '''
+ Used to get or create a new user at database associated with this authenticator.
+
+ This user has all parameter default, that are:
+ * 'real_name':realName
+ * 'last_access':NEVER
+ * 'state':State.ACTIVE
+
+ Args:
+ username: The username to create and associate with this auhtenticator
+
+ realName: If None, it will be the same that username. If otherwise especified, it will be the default real_name (field)
+
+ Returns:
+ True if the ip can access this Transport.
+
+ False if the ip can't access this Transport.
+
+ The ip check is done this way:
+ * If The associated network is empty, the result is always True
+ * If the associated network is not empty, and nets_positive (field) is True, the result will be True if
+ the ip is contained in any subnet associated with this transport.
+ * If the associated network is empty, and nets_positive (field) is False, the result will be True if
+ the ip is NOT contained in ANY subnet associated with this transport.
+
+ Raises:
+
+
+ '''
+ if realName is None:
+ realName = username
+ user, _ = self.users.get_or_create( name = username, defaults = { 'real_name':realName, 'last_access':NEVER, 'state':State.ACTIVE } )
+ if realName != user.real_name:
+ user.real_name = realName
+ user.save()
+
+ return user
+
+ def isValidUser(self, username, falseIfNotExists = True):
+ '''
+ Checks the validity of an user
+
+ Args:
+ username: Name of the user to check
+
+ falseIfNotExists: Defaults to True. It is used so we can return a value defined by caller.
+
+ One example of falseIfNotExists using as True is for checking that the user is active or it doesn't exists.
+
+ Returns:
+ True if it exists and is active, falseIfNotExists (param) if it doesn't exists
+
+ This is done so we can check non existing or non blocked users (state != Active, or do not exists)
+ '''
+ try:
+ u = self.users.get(name=username)
+ return State.isActive(u.state)
+ except Exception:
+ return falseIfNotExists
+
+ def __unicode__(self):
+ return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
+ @staticmethod
+ def all():
+ '''
+ Returns all authenticators ordered by priority
+ '''
+ return Authenticator.objects.all().order_by('priority')
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ # Only tries to get instance if data is not empty
+ if toDelete.data != '':
+ s = toDelete.getInstance()
+ s.destroy()
+ s.env().clearRelatedData()
+
+ logger.debug('Before delete auth '.format(toDelete))
+
+# Connects a pre deletion signal to Authenticator
+signals.pre_delete.connect(Authenticator.beforeDelete, sender = Authenticator)
+
+class User(models.Model):
+ '''
+ This class represents a single user, associated with one authenticator
+ '''
+ manager = models.ForeignKey(Authenticator, on_delete=models.CASCADE, related_name='users')
+ name = models.CharField(max_length = 128, db_index = True)
+ real_name = models.CharField(max_length = 128)
+ comments = models.CharField(max_length = 256)
+ state = models.CharField(max_length = 1, db_index = True)
+ password = models.CharField(max_length = 128, default = '') # Only used on "internal" sources
+ staff_member = models.BooleanField(default = False) # Staff members can login to admin
+ is_admin = models.BooleanField(default = False) # is true, this is a super-admin
+ last_access = models.DateTimeField(default=NEVER)
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ unique_together = (("manager", "name"),)
+ ordering = ('name',)
+
+ def getUsernameForAuth(self):
+ '''
+ Return the username transformed for authentication.
+ This transformation is used for transports only, not for transforming
+ anything at login time. Transports that will need the username, will invoke
+ this method.
+ The manager (an instance of uds.core.auths.Authenticator), can transform the database stored username
+ so we can, for example, add @domain in some cases.
+ '''
+ return self.getManager().getForAuth(self.name)
+
+ def getManager(self):
+ '''
+ Returns the authenticator object that owns this user.
+
+ :note: The returned value is an instance of the authenticator class used to manage this user, not a db record.
+ '''
+ return self.manager.getInstance()
+
+ def isStaff(self):
+ '''
+ Return true if this user is admin or staff member
+ '''
+ return self.staff_member or self.is_admin
+
+ def prefs(self, modName):
+ '''
+ Returns the preferences for this user for the provided module name.
+
+ Usually preferences will be associated with transports, but can be preferences registered by ANY module.
+
+ Args:
+ modName: name of the module to get preferences for
+
+
+ Returns:
+
+ The preferences for the module specified as a dictionary (can be empty if module is not found).
+
+ If the module exists, the preferences will always contain something, but may be the values are the default ones.
+
+ '''
+ from uds.core.managers.UserPrefsManager import UserPrefsManager
+ return UserPrefsManager.manager().getPreferencesForUser(modName, self)
+
+ def updateLastAccess(self):
+ '''
+ Updates the last access for this user with the current time of the sql server
+ '''
+ self.last_access = getSqlDatetime()
+ self.save()
+
+ def logout(self):
+ '''
+ Invoked to log out this user
+ '''
+ return self.getManager().logout(self.name)
+
+ def __unicode__(self):
+ return "User {0} from auth {1}".format(self.name, self.manager.name)
+
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ In this case, this method ensures that the user has no userServices assigned and, if it has,
+ mark those services for removal
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+
+ # first, we invoke removeUser. If this raises an exception, user will not
+ # be removed
+ toDelete.getManager().removeUser(toDelete.name)
+
+ # Removes all user services assigned to this user (unassign it and mark for removal)
+ for us in toDelete.userServices.all():
+ us.assignToUser(None)
+ us.remove()
+
+
+ logger.debug('Deleted user {0}'.format(toDelete))
+
+signals.pre_delete.connect(User.beforeDelete, sender = User)
+
+class Group(models.Model):
+ '''
+ This class represents a group, associated with one authenticator
+ '''
+ manager = models.ForeignKey(Authenticator, on_delete=models.CASCADE, related_name='groups')
+ name = models.CharField(max_length = 128, db_index = True)
+ state = models.CharField(max_length = 1, default = State.ACTIVE, db_index = True)
+ comments = models.CharField(max_length = 256, default = '')
+ users = models.ManyToManyField(User, related_name='groups')
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ unique_together = (("manager", "name"),)
+ ordering = ('name',)
+
+ def getManager(self):
+ '''
+ Returns the authenticator object that owns this user.
+
+ :note: The returned value is an instance of the authenticator class used to manage this user, not a db record.
+ '''
+ return self.manager.getInstance()
+
+ def __unicode__(self):
+ return "Group {0} from auth {1}".format(self.name, self.manager.name)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ In this case, this is a dummy method, waiting for something useful to do :-)
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ # Todelete is a group
+
+ # We invoke removeGroup. If this raises an exception, group will not
+ # be removed
+ toDelete.getManager().removeGroup(toDelete.name)
+
+
+ logger.debug('Deleted group {0}'.format(toDelete))
+
+signals.pre_delete.connect(Group.beforeDelete, sender = Group)
+
+class UserPreference(models.Model):
+ '''
+ This class represents a single user preference for an user and a module
+ '''
+ module = models.CharField(max_length=32, db_index = True)
+ name = models.CharField(max_length=32, db_index = True)
+ value = models.CharField(max_length=128, db_index = True)
+ user = models.ForeignKey(User, on_delete=models.CASCADE, related_name = 'preferences')
+
+# Provisioned services
+class DeployedService(models.Model):
+ '''
+ A deployed service is the Service produced element that is assigned finally to an user (i.e. a Virtual Machine, etc..)
+ '''
+ name = models.CharField(max_length=128, default = '')
+ comments = models.CharField(max_length = 256, default = '')
+ service = models.ForeignKey(Service, null=True, blank=True, related_name = 'deployedServices')
+ osmanager = models.ForeignKey(OSManager, null=True, blank=True, related_name = 'deployedServices')
+ transports = models.ManyToManyField(Transport, related_name='deployedServices', db_table = 'uds__ds_trans')
+ assignedGroups = models.ManyToManyField(Group, related_name='deployedServices', db_table = 'uds__ds_grps')
+ state = models.CharField(max_length = 1, default = State.ACTIVE, db_index = True)
+ state_date = models.DateTimeField(default=NEVER)
+ initial_srvs = models.PositiveIntegerField(default = 0)
+ cache_l1_srvs = models.PositiveIntegerField(default = 0)
+ cache_l2_srvs = models.PositiveIntegerField(default = 0)
+ max_srvs = models.PositiveIntegerField(default = 0)
+ current_pub_revision = models.PositiveIntegerField(default = 1)
+
+ class Meta:
+ '''
+ Meta class to declare the name of the table at database
+ '''
+ db_table = 'uds__deployed_service'
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def activePublication(self):
+ '''
+ Returns the current valid publication for this deployed service.
+
+ Returns:
+ Publication db record if this deployed service has an valid active publication.
+
+ None if there is no valid publication for this deployed service.
+ '''
+ try:
+ return self.publications.filter(state=State.USABLE)[0]
+ except Exception:
+ return None
+
+ def transformsUserOrPasswordForService(self):
+ return self.osmanager.getType().transformsUserOrPasswordForService()
+
+ def processUserPassword(self, username, password):
+ '''
+ This method is provided for consistency between UserService and DeployedService
+ There is no posibility to check the username and password that a user will use to
+ connect to a service at this level, because here there is no relation between both.
+
+ The only function of this method is allow Transport to transform username/password in
+ getConnectionInfo without knowing if it is requested by a DeployedService or an UserService
+ '''
+ return [username, password]
+
+ def setState(self, state, save = True):
+ '''
+ Updates the state of this object and, optionally, saves it
+
+ Args:
+ state: new State to store at record
+
+ save: Defaults to true. If false, record will not be saved to db, just modified
+
+ '''
+ self.state = state
+ self.state_date = getSqlDatetime()
+ if save is True:
+ self.save()
+
+ def remove(self):
+ '''
+ Marks the deployed service for removing.
+
+ The background worker will be the responsible for removing the deployed service
+ '''
+ self.setState(State.REMOVABLE)
+
+ def removed(self):
+ '''
+ Mark the deployed service as removed.
+
+ A background worker will check for removed deloyed services and clean database of them.
+ '''
+ self.transports.clear()
+ self.assignedGroups.clear()
+ self.osmanager = None
+ self.service = None
+ self.setState(State.REMOVED)
+
+
+ def markOldDeployedServicesAsRemovables(self, activePub):
+ '''
+ Used when a new publication is finished.
+
+ Marks all user deployed services that belongs to this deployed service, that do not belongs
+ to "activePub" and are not in use as removable.
+
+ Also cancels all preparing user services
+
+ Better see the code, it's easier to understand :-)
+
+ Args:
+ activePub: Active publication used as "current" publication to make checks
+ '''
+ now = getSqlDatetime()
+ if activePub == None:
+ logger.error('No active publication, don\'t know what to erase!!! (ds = {0})'.format(self))
+ return
+ for ap in self.publications.exclude(id=activePub.id):
+ for u in ap.userServices.filter(state=State.PREPARING):
+ u.cancel()
+ ap.userServices.exclude(cache_level=0).filter(state=State.USABLE).update(state=State.REMOVABLE, state_date = now)
+ ap.userServices.filter(cache_level=0, state=State.USABLE, in_use=False).update(state=State.REMOVABLE, state_date = now)
+
+ def validateUser(self, user):
+ '''
+ Validates that the user has access to this deployed service
+
+ Args:
+ user: User (db record) to check if has access to this deployed service
+
+ Returns:
+ True if has access
+
+ Raises:
+ InvalidUserException() if user do not has access to this deployed service
+
+ InvalidServiceException() if user has rights to access, but the deployed service is not ready (no active publication)
+
+ '''
+ # We have to check if at least one group from this user is valid for this deployed service
+ from uds.core import auths
+
+ logger.debug('User: {0}'.format(user.id))
+ logger.debug('DeployedService: {0}'.format(self.id))
+ if len( set(user.groups.all()) & set(self.assignedGroups.all()) ) == 0:
+ raise auths.Exceptions.InvalidUserException()
+ if self.activePublication() is None and self.service.getType().publicationType is not None:
+ raise InvalidServiceException()
+ return True
+
+ @staticmethod
+ def getDeployedServicesForGroups(groups):
+ '''
+ Return deployed services with publications for the groups requested.
+
+ Args:
+ groups: List of groups to check
+
+ Returns:
+ List of accesible deployed services
+ '''
+ from uds.core import services
+ list1 = DeployedService.objects.filter(assignedGroups__in=groups, assignedGroups__state__exact=State.ACTIVE, state = State.ACTIVE).distinct().annotate(cuenta=models.Count('publications')).exclude(cuenta__eq=0)
+ # Now get deployed services that DO NOT NEED publication
+ doNotNeedPublishing = [ t.type() for t in services.factory().servicesThatDoNotNeedPublication() ]
+ list2 = DeployedService.objects.filter(assignedGroups__in=groups, assignedGroups__state__exact=State.ACTIVE, service__data_type__in=doNotNeedPublishing, state = State.ACTIVE)
+ return [ r for r in list1 ] + [ r for r in list2 ]
+
+
+ def publish(self):
+ '''
+ Launches the publication of this deployed service.
+
+ No check is done, it simply redirects the request to PublicationManager, where checks are done.
+ '''
+ from uds.core.managers.PublicationManager import PublicationManager
+ PublicationManager.manager().publish(self)
+
+ def unpublish(self):
+ '''
+ Unpublish (removes) current active publcation.
+
+ It checks that there is an active publication, and then redirects the request to the publication itself
+ '''
+ pub = self.activePublication()
+ if pub is not None:
+ pub.unpublish()
+
+ def cachedUserServices(self):
+ '''
+ Utility method to access the cached user services (level 1 and 2)
+
+ Returns:
+ A list of db records (userService) with cached user services
+ '''
+ return self.userServices.exclude(cache_level=0)
+
+ def assignedUserServices(self):
+ '''
+ Utility method to access the assigned user services
+
+ Returns:
+ A list of db records (userService) with assinged user services
+ '''
+ return self.userServices.filter(cache_level=0)
+
+ def erroneousUserServices(self):
+ '''
+ Utility method to locate invalid assigned user services.
+
+ If an user deployed service is assigned, it MUST have an user associated.
+
+ If it don't has an user associated, the user deployed service is wrong.
+ '''
+ return self.userServices.filter(cache_level=0, user=None)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ toDelete.getEnvironment().clearRelatedData()
+
+ logger.debug('Deleting Deployed Service {0}'.format(toDelete))
+
+ def __unicode__(self):
+ return "Deployed service {0}({1}) with {2} as initial, {3} as L1 cache, {4} as L2 cache, {5} as max".format(
+ self.name, self.id, self.initial_srvs, self.cache_l1_srvs, self.cache_l2_srvs, self.max_srvs)
+
+
+# Connects a pre deletion signal to Authenticator
+signals.pre_delete.connect(DeployedService.beforeDelete, sender = DeployedService)
+
+class DeployedServicePublication(models.Model):
+ '''
+ A deployed service publication keep track of data needed by services that needs "preparation". (i.e. Virtual machine --> base machine --> children of base machines)
+ '''
+ deployed_service = models.ForeignKey(DeployedService, on_delete=models.CASCADE, related_name = 'publications')
+ publish_date = models.DateTimeField(db_index = True)
+ # data_type = models.CharField(max_length=128) # The data type is specified by the service itself
+ data = models.TextField(default='')
+ # Preparation state. The preparation of a service is a task that runs over time, we need to:
+ # * Prepare it
+ # * Use it
+ # * Remove it
+ # * Mark as failed
+ # The responsible class will notify when we have to change state, and a deployed service will only be usable id it has at least
+ # a prepared service "Usable" or it doesn't need to prepare anything (needsDeployment = False)
+ state = models.CharField(max_length = 1, default = State.PREPARING, db_index = True)
+ state_date = models.DateTimeField()
+ revision = models.PositiveIntegerField(default = 1)
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ db_table = 'uds__deployed_service_pub'
+ ordering = ('publish_date',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self):
+ '''
+ Instantiates the object this record contains.
+
+ Every single record of Provider model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ serviceInstance = self.deployed_service.service.getInstance()
+ osManagerInstance = self.deployed_service.osmanager
+ if osManagerInstance is not None:
+ osManagerInstance = osManagerInstance.getInstance()
+ # Sanity check, so it's easier to find when we have created
+ # a service that needs publication but do not have
+
+ if serviceInstance.publicationType is None:
+ raise Exception('Tried to get a publication instance for a service that do not needs it')
+
+ if serviceInstance.publicationType is None:
+ raise Exception('Class {0} do not have defined publicationType but needs to be published!!!'.format(serviceInstance.__class__.__name))
+
+ dpl = serviceInstance.publicationType(self.getEnvironment(), service = serviceInstance, osManager = osManagerInstance, revision = self.revision, dsName = self.deployed_service.name )
+ # Only invokes deserialization if data has something. '' is nothing
+ if self.data != '' and self.data is not None:
+ dpl.unserialize(self.data)
+ return dpl
+
+ def updateData(self, dsp):
+ '''
+ Updates the data field with the serialized uds.core.services.Publication
+
+ Args:
+ dsp: uds.core.services.Publication to serialize
+
+ :note: This method do not saves the updated record, just updates the field
+ '''
+ self.data = dsp.serialize()
+
+ def setState(self, state):
+ '''
+ Updates the state of this object and, optionally, saves it
+
+ Args:
+ state: new State to store at record
+
+ save: Defaults to true. If false, record will not be saved to db, just modified
+
+ '''
+ self.state_date = getSqlDatetime()
+ self.state = state
+
+ def unpublish(self):
+ '''
+ Tries to remove the publication
+
+ No check is done, it simply redirects the request to PublicationManager, where checks are done.
+ '''
+ from uds.core.managers.PublicationManager import PublicationManager
+ PublicationManager.manager().unpublish(self)
+
+ def cancel(self):
+ '''
+ Invoques the cancelation of this publication
+ '''
+ from uds.core.managers.PublicationManager import PublicationManager
+ PublicationManager.manager().cancel(self)
+
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ toDelete.getEnvironment().clearRelatedData()
+
+ # Destroy method is invoked directly by PublicationManager,
+ # Destroying a publication is not obligatory an 1 step action.
+ # It's handled as "publish", and as so, it
+
+ logger.debug('Deleted publication {0}'.format(toDelete))
+
+
+
+# Connects a pre deletion signal to Authenticator
+signals.pre_delete.connect(DeployedServicePublication.beforeDelete, sender = DeployedServicePublication)
+
+class UserService(models.Model):
+ '''
+ This is the base model for assigned user service and cached user services.
+ This are the real assigned services to users. DeployedService is the container (the group) of this elements.
+ '''
+ # The reference to deployed service is used to accelerate the queries for different methods, in fact its redundant cause we can access to the deployed service
+ # through publication, but queries are much more simple
+ deployed_service = models.ForeignKey(DeployedService, on_delete=models.CASCADE, related_name = 'userServices')
+ publication = models.ForeignKey(DeployedServicePublication, on_delete=models.CASCADE, null=True, blank=True, related_name = 'userServices')
+
+ unique_id = models.CharField(max_length=128, default ='', db_index = True) # User by agents to locate machine
+ friendly_name = models.CharField(max_length=128, default = '')
+ # We need to keep separated two differents os states so service operations (move beween caches, recover service) do not affects os manager state
+ state = models.CharField(max_length=1, default=State.PREPARING, db_index = True) # We set index so filters at cache level executes faster
+ os_state = models.CharField(max_length=1, default=State.PREPARING) # The valid values for this field are PREPARE and USABLE
+ state_date = models.DateTimeField(auto_now_add=True)
+ creation_date = models.DateTimeField(db_index = True)
+ data = models.TextField(default='')
+ user = models.ForeignKey(User, on_delete=models.CASCADE, related_name = 'userServices', null=True, blank=True, default = None)
+ in_use = models.BooleanField(default=False)
+ in_use_date = models.DateTimeField(default=NEVER)
+ cache_level = models.PositiveSmallIntegerField(db_index=True, default=0) # Cache level must be 1 for L1 or 2 for L2, 0 if it is not cached service
+
+ src_hostname = models.CharField(max_length=64, default='')
+ src_ip = models.CharField(max_length=15, default='')
+
+ objects = LockingManager()
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ db_table = 'uds__user_service'
+ ordering = ('creation_date',)
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents.
+
+ In the case of the user, there is an instatiation of "generators".
+ Right now, there is two generators provided to child instance objects, that are
+ valid for generating unique names and unique macs. In a future, there could be more generators
+
+ To access this generators, use the Envirnment class, and the keys 'name' and 'mac'.
+
+ (see related classes uds.core.util.UniqueNameGenerator and uds.core.util.UniqueMacGenerator)
+ '''
+ from uds.core.util.UniqueMacGenerator import UniqueMacGenerator
+ from uds.core.util.UniqueNameGenerator import UniqueNameGenerator
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id, {'mac' : UniqueMacGenerator, 'name' : UniqueNameGenerator } )
+
+ def getInstance(self):
+ '''
+ Instantiates the object this record contains. In this case, the instantiated object needs also
+ the os manager and the publication, so we also instantiate those here.
+
+ Every single record of UserService model, represents an object.
+
+ Args:
+ values (list): Values to pass to constructor. If no values are especified,
+ the object is instantiated empty and them de-serialized from stored data.
+
+ Returns:
+ The instance Instance of the class this provider represents
+
+ Raises:
+ '''
+ # We get the service instance, publication instance and osmanager instance
+ ds = self.deployed_service
+ serviceInstance = ds.service.getInstance()
+ if serviceInstance.needsManager is False:
+ osmanagerInstance = None
+ else:
+ osmanagerInstance = ds.osmanager.getInstance()
+ # We get active publication
+ publicationInstance = None
+ try: # We may have deleted publication...
+ if self.publication != None:
+ publicationInstance = self.publication.getInstance()
+ except Exception, e:
+ # The publication to witch this item points to, does not exists
+ self.publication = None
+ logger.error("Got exception at getInstance of an userService {0} : {1}".format(e.__class__, e))
+ if serviceInstance.deployedType is None:
+ raise Exception('Class {0} needs deployedType but it is not defined!!!'.format(serviceInstance.__class__.__name__))
+ us = serviceInstance.deployedType(self.getEnvironment(), service = serviceInstance, publication = publicationInstance, osmanager = osmanagerInstance, dbservice = self)
+ if self.data != '' and self.data is not None:
+ us.unserialize(self.data)
+ return us
+
+ def updateData(self, us):
+ '''
+ Updates the data field with the serialized :py:class:uds.core.services.UserDeployment
+
+ Args:
+ dsp: :py:class:uds.core.services.UserDeployment to serialize
+
+ :note: This method do not saves the updated record, just updates the field
+ '''
+ self.data = us.serialize()
+
+ def getName(self):
+ '''
+ Returns the name of the user deployed service
+ '''
+ if self.friendly_name == '':
+ si = self.getInstance()
+ self.friendly_name = si.getName()
+ self.updateData(si)
+
+ return self.friendly_name
+
+ def getUniqueId(self):
+ '''
+ Returns the unique id of the user deployed service
+ '''
+ if self.unique_id == '':
+ si = self.getInstance()
+ self.unique_id = si.getUniqueId()
+ self.updateData(si)
+ return self.unique_id
+
+ def storeValue(self, name, value):
+ '''
+ Stores a value inside custom storage
+
+ Args:
+ name: Name of the value to store
+ value: Value of the value to store
+ '''
+ self.getEnvironment().storage().put(name, value)
+
+ def recoverValue(self, name):
+ '''
+ Recovers a value from custom storage
+
+ Args:
+ name: Name of values to recover
+
+ Returns:
+ Stored value, None if no value was stored
+ '''
+ return self.getEnvironment().storage().get(name)
+
+ def setConnectionSource(self, ip, hostname=''):
+ '''
+ Notifies that the last access to this service was initiated from provided params
+
+ Args:
+ ip: Ip from where the connection was initiated
+ hostname: Hostname from where the connection was initiated
+
+ Returns:
+ Nothing
+ '''
+ self.src_ip = ip
+ self.src_hostname = hostname
+ self.save()
+
+ def getConnectionSource(self):
+ '''
+ Returns stored connection source data (ip & hostname)
+
+ Returns:
+ An array of two elements, first is the ip & second is the hostname
+
+ :note: If the transport did not notified this data, this may be "empty"
+ '''
+ return [self.src_ip, self.src_hostname]
+
+ def transformsUserOrPasswordForService(self):
+ '''
+ If the os manager changes the username or the password, this will return True
+ '''
+ return self.deployed_service.transformsUserOrPasswordForService()
+
+ def processUserPassword(self, username, password):
+ '''
+ Before accessing a service by a transport, we can request
+ the service to "transform" the username & password that the transport
+ will use to connect to that service.
+
+ This method is here so transport USE it before using the username/password
+ provided by user or by own transport configuration.
+
+ Args:
+ username: the username that will be used to connect to service
+ password: the password that will be used to connect to service
+
+ Return:
+ An array of two elements, first is transformed username, second is
+ transformed password.
+
+ :note: This method MUST be invoked by transport before using credentials passed to getHtml.
+ '''
+ ds = self.deployed_service
+ serviceInstance = ds.service.getInstance()
+ if serviceInstance.needsManager is False:
+ return [username, password]
+
+ return ds.osmanager.getInstance().processUserPassword(self, username, password)
+
+ def setState(self, state):
+ '''
+ Updates the state of this object and, optionally, saves it
+
+ Args:
+ state: new State to store at record
+
+ save: Defaults to true. If false, record will not be saved to db, just modified
+
+ '''
+ self.state_date = getSqlDatetime()
+ self.state = state
+
+ def setOsState(self, state):
+ '''
+ Updates the os state (state of the os) of this object and, optionally, saves it
+
+ Args:
+ state: new State to store at record
+
+ save: Defaults to true. If false, record will not be saved to db, just modified
+
+ '''
+ self.state_date = getSqlDatetime()
+ self.os_state = state
+
+ def assignToUser(self, user):
+ '''
+ Assigns this user deployed service to an user.
+
+ Args:
+ user: User to assing to (db record)
+ '''
+ self.cache_level = 0
+ self.user = user
+
+ def setInUse(self, state):
+ '''
+ Set the "in_use" flag for this user deployed service
+
+ Args:
+ state: State to set to the "in_use" flag of this record
+
+ :note: If the state is Fase (set to not in use), a check for removal of this deployed service is launched.
+ '''
+ from uds.core.managers.UserServiceManager import UserServiceManager
+ self.in_use = state
+ self.in_use_date = getSqlDatetime()
+ if state is False: # Service released, check y we should mark it for removal
+ # If our publication is not current, mark this for removal
+ UserServiceManager.manager().checkForRemoval(self)
+
+
+ def isUsable(self):
+ '''
+ Returns if this service is usable
+ '''
+ return State.isUsable(self.state)
+
+ def isPreparing(self):
+ '''
+ Returns if this service is in preparation (not ready to use, but in its way to be so...)
+ '''
+ return State.isPreparing(self.state)
+
+ def isReady(self):
+ '''
+ Returns if this service is ready (not preparing or marked for removal)
+ '''
+ # Call to isReady of the instance
+ from uds.core.managers.UserServiceManager import UserServiceManager
+ return UserServiceManager.manager().isReady(self)
+
+ def remove(self):
+ '''
+ Mark this user deployed service for removal
+ '''
+ self.setState(State.REMOVABLE)
+ self.save()
+
+ def cancel(self):
+ '''
+ Asks the UserServiceManager to cancel the current operation of this user deployed service.
+ '''
+ from uds.core.managers.UserServiceManager import UserServiceManager
+ UserServiceManager.manager().cancel(self)
+
+ def removeOrCancel(self):
+ '''
+ Marks for removal or cancels it, depending on state
+ '''
+ if self.isUsable():
+ self.remove()
+ else:
+ self.cancel()
+
+ def moveToLevel(self, cacheLevel):
+ '''
+ Moves cache items betwen levels, managed directly
+
+ Args:
+ cacheLevel: New cache level to put object in
+ '''
+ from uds.core.managers.UserServiceManager import UserServiceManager
+ UserServiceManager.manager().moveToLevel(self, cacheLevel)
+
+ @staticmethod
+ def getUserAssignedServices(user):
+ '''
+ Return DeployedUserServices (not deployed services) that this user owns and are assignable
+ For this to happen, we locate all user services assigned to this user, and we keep those that:
+ * Must assign service manually
+ This method is probably slow, but i don't think a user will have more than a bunch of services assigned
+ @returns and array of dicts with id, name and transports
+ '''
+ logger.debug("Filtering assigned services for user {0}".format(user))
+ res = []
+ for us in UserService.objects.filter(user=user):
+ if us.deployed_service.state != State.ACTIVE: # Do not show removing or removed services
+ continue;
+ usi = us.getInstance()
+ if usi.service().mustAssignManually is False:
+ continue
+ res.append({ 'id' : us.id, 'name' : usi.getName(), 'transports' : us.deployed_service.transports, 'service' : us })
+ return res
+
+ def __unicode__(self):
+ return "User service {0}, cache_level {1}, user {2}".format(self.id, self.cache_level, self.user)
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to invoke the Service class "Destroy" before deleting it from database.
+
+ The main purpuse of this hook is to call the "destroy" method of the object to delete and
+ to clear related data of the object (environment data such as own storage, cache, etc...
+
+ :note: If destroy raises an exception, the deletion is not taken.
+ '''
+ toDelete = kwargs['instance']
+ toDelete.getEnvironment().clearRelatedData()
+
+ # TODO: Check if this invokation goes here
+ #toDelete.getInstance()
+
+ logger.debug('Deleted user service {0}'.format(toDelete))
+
+# Connects a pre deletion signal to Authenticator
+signals.pre_delete.connect(UserService.beforeDelete, sender = UserService)
+
+# Especific loggin information for an user service
+class Log(models.Model):
+ '''
+ This class represents the log associated with an user service.
+
+ This log is mainly used to keep track of log infor relative to the service
+ (such as when a user access a machine,
+ of information related to
+ '''
+
+ owner_id = models.IntegerField(db_index=True, default=0)
+ owner_type = models.IntegerField(db_index=True, default=0)
+
+ created = models.DateTimeField(auto_now_add=True, db_index=True)
+ source = models.CharField(max_length=16, default='internal', db_index=True)
+ level = models.PositiveSmallIntegerField(default=0, db_index=True)
+ data = models.CharField(max_length=255, default='')
+
+
+ class Meta:
+ '''
+ Meta class to declare db table
+ '''
+ db_table = 'uds_log'
+
+
+ def __unicode__(self):
+ return "Log of {0}({1}): {2} - {3} - {4} - {5}".format(self.owner_type, self.owner_id, self.created, self.source, self.level, self.data)
+
+
+# General utility models, such as a database cache (for caching remote content of slow connections to external services providers for example)
+# We could use django cache (and maybe we do it in a near future), but we need to clean up things when objecs owning them are deleted
+class Cache(models.Model):
+ '''
+ General caching model. This model is managed via uds.core.util.Cache.Cache class
+ '''
+ owner = models.CharField(max_length = 128, db_index = True)
+ key = models.CharField(max_length = 64, primary_key = True)
+ value = models.TextField(default = '')
+ created = models.DateTimeField() # Date creation or validation of this entry. Set at write time
+ validity = models.IntegerField(default = 60) # Validity of this entry, in seconds
+
+ class Meta:
+ '''
+ Meta class to declare the name of the table at database
+ '''
+ db_table = 'uds_utility_cache'
+
+ @staticmethod
+ def cleanUp():
+ '''
+ Purges the cache items that are no longer vaild.
+ '''
+ from django.db import connection, transaction
+ con = connection
+ cursor = con.cursor()
+ logger.info("Purging cache items")
+ cursor.execute('DELETE FROM uds_utility_cache WHERE created + validity < now()')
+ transaction.commit_unless_managed()
+
+
+ def __unicode__(self):
+ expired = datetime.now() > self.created + timedelta(seconds = self.validity)
+ if expired:
+ expired = "Expired"
+ else:
+ expired = "Active"
+ return "{0} {1} = {2} ({3})".format(self.owner, self.key, self.value, expired)
+
+class Config(models.Model):
+ '''
+ General configuration values model. Used to store global and specific modules configuration values.
+ This model is managed via uds.core.util.Config.Config class
+ '''
+ section = models.CharField(max_length=128)
+ key = models.CharField(max_length=64)
+ value = models.TextField(default = '')
+ crypt = models.BooleanField(default = False)
+ long = models.BooleanField(default = False)
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ db_table = 'uds_configuration'
+ unique_together = (('section', 'key'),)
+
+ def __unicode__(self):
+ return "Config {0} = {1}".format(self.key, self.value)
+
+class Storage(models.Model):
+ '''
+ General storage model. Used to store specific instances (transport, service, servicemanager, ...) persinstent information
+ not intended to be serialized/deserialized everytime one object instance is loaded/saved.
+ '''
+ owner = models.CharField(max_length = 128, db_index = True)
+ key = models.CharField(max_length = 64, primary_key = True)
+ data = models.TextField(default = '')
+ attr1 = models.CharField(max_length = 64, db_index = True, null=True, blank=True, default = None)
+
+ objects = LockingManager()
+
+ def __unicode__(self):
+ return "{0} {1} = {2}, {3}".format(self.owner, self.key, self.data, str.join( '/', [self.attr1]))
+
+class UniqueId(models.Model):
+ '''
+ Unique ID Database. Used to store unique names, unique macs, etc...
+ Managed via uds.core.util.UniqueIDGenerator.UniqueIDGenerator
+ '''
+ owner = models.CharField(max_length = 128, db_index = True, default = '')
+ basename = models.CharField(max_length = 32, db_index = True)
+ seq = models.BigIntegerField(db_index=True)
+ assigned = models.BooleanField(db_index=True, default = True)
+
+ objects = LockingManager()
+
+ class Meta:
+ '''
+ Meta class to declare default order and unique multiple field index
+ '''
+ unique_together = (('basename', 'seq'),)
+ ordering = ('-seq',)
+
+
+ def __unicode__(self):
+ return "{0} {1}.{2}, assigned is {3}".format(self.owner, self.basename, self.seq, self.assigned)
+
+
+class Scheduler(models.Model):
+ '''
+ Class that contains scheduled tasks.
+
+ The scheduled task are keep at database so:
+ * We can access them from any host
+ * We have a persistence for them
+
+ The Scheduler contains jobs, that are clases that manages the job.
+ Jobs are not serialized/deserialized, they are just Task delegated to certain clases.
+
+ In order for a task to work, it must first register itself for "names" that that class handles using the
+ JobsFactory
+ '''
+
+ DAY = 60*60*24
+ HOUR = 60*60
+ MIN = 60
+
+ name = models.CharField(max_length = 64, unique = True)
+ frecuency = models.PositiveIntegerField(default = DAY)
+ last_execution = models.DateTimeField(auto_now_add = True)
+ next_execution = models.DateTimeField(default = NEVER, db_index = True)
+ owner_server = models.CharField(max_length=64, db_index = True, default = '')
+ state = models.CharField(max_length = 1, default = State.FOR_EXECUTE, db_index = True)
+
+ #objects = LockingManager()
+
+ def getEnvironment(self):
+ '''
+ Returns an environment valid for the record this object represents
+ '''
+ return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
+
+ def getInstance(self):
+ '''
+ Returns an instance of the class that this record of the Scheduler represents. This clas is derived
+ of uds.core.jobs.Job.Job
+ '''
+ jobInstance = JobsFactory.factory().lookup(self.name)
+ if jobInstance != None:
+ env = self.getEnvironment()
+ return jobInstance(env)
+ else:
+ return None
+
+ @staticmethod
+ def beforeDelete(sender, **kwargs):
+ '''
+ Used to remove environment for sheduled task
+ '''
+ toDelete = kwargs['instance']
+ logger.debug('Deleting sheduled task {0}'.format(toDelete))
+ toDelete.getEnvironment().clearRelatedData()
+
+
+ def __unicode__(self):
+ return "Scheduled task {0}, every {1}, last execution at {2}, state = {3}".format(self.name, self.frecuency, self.last_execution, self.state)
+
+# Connects a pre deletion signal to Scheduler
+signals.pre_delete.connect(Scheduler.beforeDelete, sender = Scheduler)
+
+
+class DelayedTask(models.Model):
+ '''
+ A delayed task is a kind of scheduled task. It's a task that has more than is executed at a delay
+ specified at record. This is, for example, for publications, service preparations, etc...
+
+ The delayed task is different from scheduler in the fact that they are "one shot", meaning this that when the
+ specified delay is reached, the task is executed and the record is removed from the table.
+
+ This table contains uds.core.util.jobs.DelayedTask references
+ '''
+ type = models.CharField(max_length=128)
+ tag = models.CharField(max_length=64, db_index = True) # A tag for letting us locate delayed publications...
+ instance = models.TextField()
+ insert_date = models.DateTimeField(auto_now_add = True)
+ execution_delay = models.PositiveIntegerField()
+ execution_time = models.DateTimeField(db_index = True)
+
+ #objects = LockingManager()
+
+ def __unicode__(self):
+ return "Run Queue task {0} owned by {3},inserted at {1} and with {2} seconds delay".format(self.type, self.insert_date, self.execution_delay, self.owner_server)
+
+
+class Network(models.Model):
+ '''
+ This model is used for keeping information of networks associated with transports (right now, just transports..)
+ '''
+ name = models.CharField(max_length = 64, unique = True)
+ net_start = models.BigIntegerField(db_index = True)
+ net_end = models.BigIntegerField(db_index = True)
+ transports = models.ManyToManyField(Transport, related_name='networks', db_table='uds_net_trans')
+
+ @staticmethod
+ def ipToLong(ip):
+ '''
+ convert decimal dotted quad string to long integer
+ '''
+ try:
+ hexn = ''.join(["%02X" % long(i) for i in ip.split('.')])
+ return long(hexn, 16)
+ except:
+ return 0 # Invalid values will map to "0.0.0.0" --> 0
+
+ @staticmethod
+ def longToIp(n):
+ '''
+ convert long int to dotted quad string
+ '''
+ try:
+ d = 256 * 256 * 256
+ q = []
+ while d > 0:
+ m,n = divmod(n,d)
+ q.append(str(m))
+ d = d/256
+
+ return '.'.join(q)
+ except:
+ return '0.0.0.0' # Invalid values will map to "0.0.0.0"
+
+ @staticmethod
+ def networksFor(ip):
+ '''
+ Returns the networks that are valid for specified ip in dotted quad (xxx.xxx.xxx.xxx)
+ '''
+ ip = Network.ipToLong(ip)
+ return Network.objects.filter(net_start__lte=ip, net_end__gte=ip)
+
+ @staticmethod
+ def create(name, netStart, netEnd):
+ '''
+ Creates an network record, with the specified net start and net end (dotted quad)
+
+ Args:
+ netStart: Network start
+
+ netEnd: Network end
+ '''
+ return Network.objects.create(name=name, net_start = Network.ipToLong(netStart), net_end = Network.ipToLong(netEnd))
+
+ @property
+ def netStart(self):
+ '''
+ Property to access the quad dotted format of the stored network start
+
+ Returns:
+ string representing the dotted quad of this network start
+ '''
+ return Network.longToIp(self.net_start)
+
+ @property
+ def netEnd(self):
+ '''
+ Property to access the quad dotted format of the stored network end
+
+ Returns:
+ string representing the dotted quad of this network end
+ '''
+ return Network.longToIp(self.net_end)
+
+ def update(self, name, netStart, netEnd):
+ '''
+ Updated this network with provided values
+
+ Args:
+ name: new name of the network
+
+ netStart: new Network start (quad dotted)
+
+ netEnd: new Network end (quad dotted)
+ '''
+ self.name = name
+ self.net_start = Network.ipToLong(netStart)
+ self.net_end = Network.ipToLong(netEnd)
+ self.save()
+
+ def __unicode__(self):
+ return 'Network {0} from {1} to {2}'.format(self.name, Network.longToIp(self.net_start), Network.longToIp(self.net_end))
+
diff --git a/trunk/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py b/trunk/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py
new file mode 100644
index 00000000..d914a02f
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core import osmanagers
+from uds.core.util.State import State
+
+import logging
+from uds.core.managers.UserServiceManager import UserServiceManager
+
+logger = logging.getLogger(__name__)
+
+class LinuxOsManager(osmanagers.OSManager):
+ typeName = _('Linux OS Manager')
+ typeType = 'LinuxManager'
+ typeDescription = _('Os Manager to control linux virtual machines (basically renames machine and notify state)')
+ iconFile = 'losmanager.png'
+
+ onLogout = gui.ChoiceField( label = _('On Logout'), order = 10, rdonly = False, tooltip = _('What to do when user logout from service'),
+ values = [ {'id' : 'keep', 'text' : _('Keep service assigned') },
+ {'id' : 'remove', 'text' : _('Remove service') }
+ ], defvalue = 'keep' )
+
+ def __init__(self,environment, values):
+ super(LinuxOsManager, self).__init__(environment, values)
+ if values is not None:
+ self._onLogout = values['onLogout']
+ else:
+ self._onLogout = ''
+
+
+ def release(self, service):
+ pass
+
+ def getName(self, service):
+ '''
+ gets name from deployed
+ '''
+ return service.getName()
+
+ def infoVal(self,service):
+ return 'rename:' + self.getName(service)
+
+ def infoValue(self,service):
+ return 'rename\r' + self.getName(service)
+
+ def notifyIp(self, uid, si, data):
+ # Notifies IP to deployed
+ pairs = data.split(',')
+ for p in pairs:
+ key, val = p.split('=')
+ if key.lower() == uid.lower():
+ si.setIp(val)
+ break
+
+ def process(self,service,msg, data):
+ '''
+ We understand this messages:
+ * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), old method
+ * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), new method
+ * msg = logon, data = Username, Informs that the username has logged in inside the machine
+ * msg = logoff, data = Username, Informs that the username has logged out of the machine
+ * msg = ready, data = None, Informs machine ready to be used
+ '''
+ logger.info("Invoked LinuxOsManager for {0} with params: {1},{2}".format(service, msg, data))
+ # We get from storage the name for this service. If no name, we try to assign a new one
+ ret = "ok"
+ inUse = False
+ notifyReady = False
+ doRemove = False
+ state = service.os_state
+
+ # Old "info" state, will be removed in a near future
+ if msg == "info":
+ ret = self.infoVal(service)
+ state = State.PREPARING
+ elif msg == "information":
+ ret = self.infoValue(service)
+ state = State.PREPARING
+ elif msg == "login":
+ si = service.getInstance()
+ si.userLoggedIn(data)
+ service.updateData(si)
+ inUse = True
+ elif msg == "logout":
+ si = service.getInstance()
+ si.userLoggedOut(data)
+ service.updateData(si)
+ if self._onLogout == 'remove':
+ doRemove = True
+ elif msg == "ip":
+ # This ocurss on main loop inside machine, so service is usable
+ state = State.USABLE
+ si = service.getInstance()
+ self.notifyIp(service.unique_id, si, data)
+ service.updateData(si)
+ elif msg == "ready":
+ state = State.USABLE
+ si = service.getInstance()
+ notifyReady = True
+ self.notifyIp(service.unique_id, si, data)
+ service.updateData(si)
+ service.setInUse(inUse)
+ service.setOsState(state)
+ # If notifyReady is not true, save state, let UserServiceManager do it for us else
+ if doRemove is True:
+ service.remove()
+ else:
+ if notifyReady is False:
+ service.save()
+ else:
+ UserServiceManager.manager().notifyReadyFromOsManager(service, '')
+ logger.debug('Returning {0}'.format(ret))
+ return ret
+
+ def checkState(self,service):
+ logger.debug('Checking state for service {0}'.format(service))
+ return State.RUNNING
+
+ def marshal(self):
+ '''
+ Serializes the os manager data so we can store it in database
+ '''
+ return str.join( '\t', [ 'v1', self._onLogout ] )
+
+ def unmarshal(self, s):
+ data = s.split('\t')
+ if data[0] == 'v1':
+ self._onLogout = data[1]
+
+ def valuesDict(self):
+ return { 'onLogout' : self._onLogout }
diff --git a/trunk/server/src/uds/osmanagers/LinuxOsManager/__init__.py b/trunk/server/src/uds/osmanagers/LinuxOsManager/__init__.py
new file mode 100644
index 00000000..e93c0d11
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/LinuxOsManager/__init__.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.osmanagers.OSManagersFactory import OSManagersFactory
+from uds.core.managers.DownloadsManager import DownloadsManager
+from uds.osmanagers.LinuxOsManager.LinuxOsManager import LinuxOsManager
+import os.path, sys
+
+OSManagersFactory.factory().insert(LinuxOsManager)
+
+DownloadsManager.manager().registerDownloadable('udsactor_1.0_all.deb',
+ _('UDS Actor for linux machines (Requires python 2.6 or greater)'),
+ os.path.dirname(sys.modules[__package__].__file__) + '/files/udsactor_1.0_all.deb',
+ 'application/x-debian-package')
diff --git a/trunk/server/src/uds/osmanagers/LinuxOsManager/losmanager.png b/trunk/server/src/uds/osmanagers/LinuxOsManager/losmanager.png
new file mode 100644
index 00000000..9a8c5864
Binary files /dev/null and b/trunk/server/src/uds/osmanagers/LinuxOsManager/losmanager.png differ
diff --git a/trunk/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py b/trunk/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py
new file mode 100644
index 00000000..6f82775e
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core.managers.CryptoManager import CryptoManager
+from uds.core import osmanagers
+from WindowsOsManager import WindowsOsManager, scrambleMsg
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class WinDomainOsManager(WindowsOsManager):
+ typeName = _('Windows Domain OS Manager')
+ typeType = 'WinDomainManager'
+ typeDescription = _('Os Manager to control windows machines with domain. (Basically renames machine)')
+ iconFile = 'wosmanager.png'
+
+ # Apart form data from windows os manager, we need also domain and credentials
+ domain = gui.TextField(length=64, label = _('Domain'), order = 1, tooltip = _('Domain to join machines to (better use dns form of domain)'), required = True)
+ account = gui.TextField(length=64, label = _('Account'), order = 2, tooltip = _('Account with rights to add machines to domain'), required = True)
+ password = gui.PasswordField(length=64, label = _('Password'), order = 3, tooltip = _('Password of the account'), required = True)
+ ou = gui.TextField(length=64, label = _('OU'), order = 4, tooltip = _('Organizational unit where to add machines in domain (check it before using it)'))
+ # Inherits base "onLogout"
+ onLogout = WindowsOsManager.onLogout
+
+ def __init__(self,environment, values):
+ super(WinDomainOsManager, self).__init__(environment, values)
+ if values != None:
+ if values['domain'] == '':
+ raise osmanagers.OSManager.ValidationException(_('Must provide a domain!!!'))
+ if values['account'] == '':
+ raise osmanagers.OSManager.ValidationException(_('Must provide an account to add machines to domain!!!'))
+ if values['password'] == '':
+ raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!!!'))
+ self._domain = values['domain']
+ self._ou = values['ou']
+ self._account = values['account']
+ self._password = values['password']
+ else:
+ self._domain = ""
+ self._ou = ""
+ self._account = ""
+ self._password = ""
+
+ def release(self, service):
+ super(WinDomainOsManager,self).release(service)
+ # TODO: remove machine from active directory os, under ou or default location if not specified
+
+ def infoVal(self, service):
+ return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format( self.getName(service), self._domain, self._ou, self._account, self._password)
+
+ def infoValue(self, service):
+ return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format( self.getName(service), self._domain, self._ou, self._account, self._password)
+
+ def marshal(self):
+ base = super(WinDomainOsManager,self).marshal()
+ '''
+ Serializes the os manager data so we can store it in database
+ '''
+ return str.join( '\t', [ 'v1', self._domain, self._ou, self._account, CryptoManager.manager().encrypt(self._password), base.encode('hex') ] )
+
+ def unmarshal(self, s):
+ data = s.split('\t')
+ if data[0] == 'v1':
+ self._domain = data[1]
+ self._ou = data[2]
+ self._account = data[3]
+ self._password = CryptoManager.manager().decrypt(data[4])
+ super(WinDomainOsManager, self).unmarshal(data[5].decode('hex'))
+
+ def valuesDict(self):
+ dict = super(WinDomainOsManager,self).valuesDict()
+ dict['domain'] = self._domain
+ dict['ou'] = self._ou
+ dict['account'] = self._account
+ dict['password'] = self._password
+ return dict
+
diff --git a/trunk/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py b/trunk/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py
new file mode 100644
index 00000000..90cc3a87
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core.managers.CryptoManager import CryptoManager
+from uds.core import osmanagers
+from WindowsOsManager import WindowsOsManager, scrambleMsg
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class WinRandomPassManager(WindowsOsManager):
+ typeName = _('Windows Random Password OS Manager')
+ typeType = 'WinRandomPasswordManager'
+ typeDescription = _('Os Manager to control windows machines, with user password set randomly.')
+ iconFile = 'wosmanager.png'
+
+ # Apart form data from windows os manager, we need also domain and credentials
+ userAccount = gui.TextField(length=64, label = _('Account'), order = 2, tooltip = _('User account to change password'), required = True)
+ password = gui.PasswordField(length=64, label = _('Password'), order = 3, tooltip = _('Current (template) password of the user account'), required = True)
+
+ # Inherits base "onLogout"
+ onLogout = WindowsOsManager.onLogout
+
+ def __init__(self,environment, values):
+ super(WinRandomPassManager, self).__init__(environment, values)
+ if values != None:
+ if values['userAccount'] == '':
+ raise osmanagers.OSManager.ValidationException(_('Must provide an user account!!!'))
+ if values['password'] == '':
+ raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!!!'))
+ self._userAccount = values['userAccount']
+ self._password = values['password']
+ else:
+ self._userAccount = ''
+ self._password = ""
+
+ def release(self, service):
+ super(WinRandomPassManager,self).release(service)
+
+ def processUserPassword(self, service, username, password):
+ if username == self._userAccount:
+ return [username, service.recoverValue('winOsRandomPass')]
+ return [username, password]
+
+ def genPassword(self, service):
+ import random
+ import string
+ randomPass = service.recoverValue('winOsRandomPass')
+ if randomPass is None:
+ randomPass = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16))
+ service.storeValue('winOsRandomPass', randomPass)
+ return randomPass
+
+ def infoVal(self, service):
+ return 'rename:{0}\t{1}\t{2}\t{3}'.format( self.getName(service), self._userAccount, self._password, self.genPassword(service))
+
+ def infoValue(self, service):
+ return 'rename\r{0}\t{1}\t{2}\t{3}'.format( self.getName(service), self._userAccount, self._password, self.genPassword(service))
+
+ def marshal(self):
+ base = super(WinRandomPassManager,self).marshal()
+ '''
+ Serializes the os manager data so we can store it in database
+ '''
+ return str.join( '\t', [ 'v1', self._userAccount, CryptoManager.manager().encrypt(self._password), base.encode('hex') ] )
+
+ def unmarshal(self, s):
+ data = s.split('\t')
+ if data[0] == 'v1':
+ self._userAccount = data[1]
+ self._password = CryptoManager.manager().decrypt(data[2])
+ super(WinRandomPassManager, self).unmarshal(data[3].decode('hex'))
+
+ def valuesDict(self):
+ dic = super(WinRandomPassManager,self).valuesDict()
+ dic['userAccount'] = self._userAccount
+ dic['password'] = self._password
+ return dic
+
diff --git a/trunk/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py b/trunk/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py
new file mode 100644
index 00000000..c83037b6
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.ui.UserInterface import gui
+from uds.core import osmanagers
+from uds.core.managers.UserServiceManager import UserServiceManager
+from uds.core.util.State import State
+from uds.core.util import log
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+def scrambleMsg(data):
+ '''
+ Simple scrambler so password are not seen at source page
+ '''
+ res = []
+ n = 0x32
+ for c in data[::-1]:
+ res.append( chr(ord(c) ^ n) )
+ n = (n + ord(c)) & 0xFF
+ return "".join(res).encode('hex')
+
+
+class WindowsOsManager(osmanagers.OSManager):
+ typeName = _('Windows Basic OS Manager')
+ typeType = 'WindowsManager'
+ typeDescription = _('Os Manager to control windows machines without domain. (Basically renames machine)')
+ iconFile = 'wosmanager.png'
+
+ onLogout = gui.ChoiceField( label = _('On Logout'), order = 10, rdonly = False, tooltip = _('What to do when user logout from service'),
+ values = [ {'id' : 'keep', 'text' : _('Keep service assigned') },
+ {'id' : 'remove', 'text' : _('Remove service') }
+ ], defvalue = 'keep' )
+
+
+ @staticmethod
+ def validateLen(len):
+ try:
+ len = int(len)
+ except Exception:
+ raise osmanagers.OSManager.ValidationException(_('Length must be numeric!!'))
+ if len > 6 or len < 1:
+ raise osmanagers.OSManager.ValidationException(_('Length must be betwen 1 and six'))
+ return len
+
+ def __init__(self,environment, values):
+ super(WindowsOsManager, self).__init__(environment, values)
+ if values is not None:
+ self._onLogout = values['onLogout']
+ else:
+ self._onLogout = ''
+
+ def release(self, service):
+ pass
+
+ def getName(self, service):
+ '''
+ gets name from deployed
+ '''
+ return service.getName()
+
+ def infoVal(self,service):
+ return 'rename:' + self.getName(service)
+
+ def infoValue(self,service):
+ return 'rename\r' + self.getName(service)
+
+ def notifyIp(self, uid, si, data):
+ # Notifies IP to deployed
+ pairs = data.split(',')
+ for p in pairs:
+ key, val = p.split('=')
+ if key.lower() == uid.lower():
+ si.setIp(val)
+ break
+
+ def doLog(self, service, data, origin = log.OSMANAGER):
+ # Stores a log associated with this service
+ from uds.core.managers import logManager
+ try:
+
+ msg, level = data.split('\t')
+ logManager().doLog(service, level, msg, origin)
+ except:
+ logManager().doLog(service, log.ERROR, "do not understand {0}".format(data), origin)
+
+
+ def process(self,service,msg, data):
+ '''
+ We understand this messages:
+ * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method)
+ * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (new method)
+ * msg = logon, data = Username, Informs that the username has logged in inside the machine
+ * msg = logoff, data = Username, Informs that the username has logged out of the machine
+ * msg = ready, data = None, Informs machine ready to be used
+ '''
+ logger.info("Invoked WindowsOsManager for {0} with params: {1},{2}".format(service, msg, data))
+ # We get from storage the name for this service. If no name, we try to assign a new one
+ ret = "ok"
+ inUse = False
+ notifyReady = False
+ doRemove = False
+ state = service.os_state
+ if msg == "info":
+ ret = self.infoVal(service)
+ state = State.PREPARING
+ elif msg == "information":
+ ret = self.infoValue(service)
+ state = State.PREPARING
+ elif msg == "log":
+ self.doLog(service, data, log.ACTOR)
+ elif msg == "logon":
+ si = service.getInstance()
+ si.userLoggedIn(data)
+ service.updateData(si)
+ self.doLog(service, 'User {0} has logged IN\t{1}'.format(data, log.INFOSTR))
+ # We get the service logged hostname & ip and returns this
+ ip, hostname = service.getConnectionSource()
+ ret = "{0}\t{1}".format(ip, hostname)
+ inUse = True
+ elif msg == "logoff":
+ si = service.getInstance()
+ si.userLoggedOut(data)
+ service.updateData(si)
+ self.doLog(service, 'User {0} has logged OUT\t{1}'.format(data, log.INFOSTR))
+ if self._onLogout == 'remove':
+ doRemove = True
+ elif msg == "ip":
+ # This ocurss on main loop inside machine, so service is usable
+ state = State.USABLE
+ si = service.getInstance()
+ self.notifyIp(service.unique_id, si, data)
+ service.updateData(si)
+ elif msg == "ready":
+ state = State.USABLE
+ si = service.getInstance()
+ notifyReady = True
+ self.notifyIp(service.unique_id, si, data)
+ service.updateData(si)
+ service.setInUse(inUse)
+ service.setOsState(state)
+ # If notifyReady is not true, save state, let UserServiceManager do it for us else
+ if doRemove is True:
+ service.remove()
+ else:
+ if notifyReady is False:
+ service.save()
+ else:
+ UserServiceManager.manager().notifyReadyFromOsManager(service, '')
+ logger.debug('Returning {0}'.format(ret))
+ return scrambleMsg(ret)
+
+ def checkState(self,service):
+ logger.debug('Checking state for service {0}'.format(service))
+ return State.RUNNING
+
+ def marshal(self):
+ '''
+ Serializes the os manager data so we can store it in database
+ '''
+ return str.join( '\t', [ 'v1', self._onLogout ] )
+
+ def unmarshal(self, s):
+ data = s.split('\t')
+ if data[0] == 'v1':
+ self._onLogout = data[1]
+
+ def valuesDict(self):
+ return { 'onLogout' : self._onLogout }
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/osmanagers/WindowsOsManager/__init__.py b/trunk/server/src/uds/osmanagers/WindowsOsManager/__init__.py
new file mode 100644
index 00000000..c5d475ad
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/WindowsOsManager/__init__.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _
+from uds.core.osmanagers.OSManagersFactory import OSManagersFactory
+from uds.core.managers.DownloadsManager import DownloadsManager
+from WindowsOsManager import WindowsOsManager
+from WinDomainOsManager import WinDomainOsManager
+from WinRandomPassOsManager import WinRandomPassManager
+import os.path, sys
+
+OSManagersFactory.factory().insert(WindowsOsManager)
+OSManagersFactory.factory().insert(WinDomainOsManager)
+OSManagersFactory.factory().insert(WinRandomPassManager)
+
+DownloadsManager.manager().registerDownloadable('UDSActorSetup.exe',
+ _('UDS Actor for windows machines (Important!! Requires .net framework 3.5 sp1)'),
+ os.path.dirname(sys.modules[__package__].__file__) + '/files/UDSActorSetup.exe',
+ 'application/x-msdos-program')
diff --git a/trunk/server/src/uds/osmanagers/WindowsOsManager/wosmanager.png b/trunk/server/src/uds/osmanagers/WindowsOsManager/wosmanager.png
new file mode 100644
index 00000000..cefdb807
Binary files /dev/null and b/trunk/server/src/uds/osmanagers/WindowsOsManager/wosmanager.png differ
diff --git a/trunk/server/src/uds/osmanagers/__init__.py b/trunk/server/src/uds/osmanagers/__init__.py
new file mode 100644
index 00000000..1e4da28c
--- /dev/null
+++ b/trunk/server/src/uds/osmanagers/__init__.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+OS Manager modules for uds are contained inside this package.
+To create a new OS manager module, you will need to follow this steps:
+ 1.- Create the os manager module, probably based on an existing one
+ 2.- Insert the module package as child of this package
+ 3.- Import the class of your os manager module at __init__. For example::
+ from OSManager import SimpleOSManager
+ 4.- Done. At Server restart, the module will be recognized, loaded and treated
+
+The registration of modules is done locating subclases of :py:class:`uds.core.auths.Authentication`
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+def __init__():
+ '''
+ This imports all packages that are descendant of this package, and, after that,
+ it register all subclases of service provider as
+ '''
+ import os.path, pkgutil
+ import sys
+ from uds.core import osmanagers
+
+ # Dinamycally import children of this package. The __init__.py files must register, if needed, inside ServiceProviderFactory
+ pkgpath = os.path.dirname(sys.modules[__name__].__file__)
+ for _, name, _ in pkgutil.iter_modules([pkgpath]):
+ __import__(name, globals(), locals(), [], -1)
+
+ p = osmanagers.OSManager
+ # This is marked as error in IDE, but it's not (__subclasses__)
+ for cls in p.__subclasses__():
+ osmanagers.factory().insert(cls)
+
+__init__()
diff --git a/trunk/server/src/uds/services/OVirt/Helpers.py b/trunk/server/src/uds/services/OVirt/Helpers.py
new file mode 100644
index 00000000..2613501b
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/Helpers.py
@@ -0,0 +1,44 @@
+'''
+Created on Nov 15, 2012
+
+@author: dkmaster
+'''
+
+from django.utils.translation import ugettext as _
+import logging
+
+logger = logging.getLogger(__name__)
+
+class oVirtHelpers(object):
+
+ @staticmethod
+ def getResources(parameters):
+ '''
+ This helper is designed as a callback for machine selector, so we can provide valid clusters and datastores domains based on it
+ '''
+ from OVirtProvider import Provider
+ from uds.core.Environment import Environment
+ logger.debug('Parameters received by getResources Helper: {0}'.format(parameters))
+ env = Environment(parameters['ev'])
+ provider = Provider(env)
+ provider.unserialize(parameters['ov'])
+
+ # Obtains datacenter from cluster
+ ci = provider.getClusterInfo(parameters['cluster'])
+
+ res = []
+ # Get storages for that datacenter
+ for storage in provider.getDatacenterInfo(ci['datacenter_id'])['storage']:
+ if storage['type'] == 'data':
+ space, free = storage['available']/1024/1024/1024, (storage['available']-storage['used'])/1024/1024/1024
+
+ res.append( {'id': storage['id'], 'text': "%s (%4.2f Gb/%4.2f Gb) %s" % (storage['name'], space, free, storage['active'] and '(ok)' or '(disabled)' ) })
+ data = [{
+ 'name' : 'datastore', 'values' : res
+ }]
+
+ logger.debug('return data: {0}'.format(data))
+ return data
+
+
+
diff --git a/trunk/server/src/uds/services/OVirt/OVirtLinkedDeployment.py b/trunk/server/src/uds/services/OVirt/OVirtLinkedDeployment.py
new file mode 100644
index 00000000..03f8049f
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/OVirtLinkedDeployment.py
@@ -0,0 +1,583 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.services import UserDeployment
+from uds.core.util.State import State
+import cPickle
+import logging
+
+logger = logging.getLogger(__name__)
+
+opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry, opChangeMac = range(10)
+
+class OVirtLinkedDeployment(UserDeployment):
+ '''
+ This class generates the user consumable elements of the service tree.
+
+ After creating at administration interface an Deployed Service, UDS will
+ create consumable services for users using UserDeployment class as
+ provider of this elements.
+
+ The logic for managing ovirt deployments (user machines in this case) is here.
+
+ '''
+
+ #: Recheck every six seconds by default (for task methods)
+ suggestedTime = 6
+
+ def initialize(self):
+ self._name = ''
+ self._ip = ''
+ self._mac = ''
+ self._vmid = ''
+ self._reason = ''
+ self._queue = []
+
+ # Serializable needed methods
+ def marshal(self):
+ '''
+ Does nothing right here, we will use envoronment storage in this sample
+ '''
+ return '\1'.join( ['v1', self._name, self._ip, self._mac, self._vmid, self._reason, cPickle.dumps(self._queue)] )
+
+ def unmarshal(self, str_):
+ '''
+ Does nothing here also, all data are keeped at environment storage
+ '''
+ vals = str_.split('\1')
+ if vals[0] == 'v1':
+ self._name, self._ip, self._mac, self._vmid, self._reason, queue = vals[1:]
+ self._queue = cPickle.loads(queue)
+
+ def getName(self):
+ '''
+ We override this to return a name to display. Default inplementation
+ (in base class), returns getUniqueIde() value
+ This name will help user to identify elements, and is only used
+ at administration interface.
+
+ We will use here the environment name provided generator to generate
+ a name for this element.
+
+ The namaGenerator need two params, the base name and a length for a
+ numeric incremental part for generating unique names. This are unique for
+ all UDS names generations, that is, UDS will not generate this name again
+ until this name is freed, or object is removed, what makes its environment
+ to also get removed, that makes all uniques ids (names and macs right now)
+ to also get released.
+
+ Every time get method of a generator gets called, the generator creates
+ a new unique name, so we keep the first generated name cached and don't
+ generate more names. (Generator are simple utility classes)
+ '''
+ if self._name == '':
+ self._name = self.nameGenerator().get( self.service().getBaseName(), self.service().getLenName() )
+ return self._name
+
+
+ def setIp(self, ip):
+ '''
+ In our case, there is no OS manager associated with this, so this method
+ will never get called, but we put here as sample.
+
+ Whenever an os manager actor notifies the broker the state of the service
+ (mainly machines), the implementation of that os manager can (an probably will)
+ need to notify the IP of the deployed service. Remember that UDS treats with
+ IP services, so will probable needed in every service that you will create.
+ :note: This IP is the IP of the "consumed service", so the transport can
+ access it.
+ '''
+ logger.debug('Setting IP to %s' % ip)
+ self._ip = ip
+
+ def getUniqueId(self):
+ '''
+ Return and unique identifier for this service.
+ In our case, we will generate a mac name, that can be also as sample
+ of 'mac' generator use, and probably will get used something like this
+ at some services.
+
+ The get method of a mac generator takes one param, that is the mac range
+ to use to get an unused mac.
+ '''
+ if self._mac == '':
+ self._mac = self.macGenerator().get( self.service().getMacRange() )
+ return self._mac
+
+ def getIp(self):
+ '''
+ We need to implement this method, so we can return the IP for transports
+ use. If no IP is known for this service, this must return None
+
+ If our sample do not returns an IP, IP transport will never work with
+ this service. Remember in real cases to return a valid IP address if
+ the service is accesible and you alredy know that (for example, because
+ the IP has been assigend via setIp by an os manager) or because
+ you get it for some other method.
+
+ Storage returns None if key is not stored.
+
+ :note: Keeping the IP address is responsibility of the User Deployment.
+ Every time the core needs to provide the service to the user, or
+ show the IP to the administrator, this method will get called
+
+ '''
+ return self._ip
+
+ def setReady(self):
+ '''
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+ '''
+ if self.cache().get('ready') == '1':
+ return State.FINISHED
+
+ state = self.service().getMachineState(self._vmid)
+
+ if state == 'unknown':
+ return self.__error('Machine is not available anymore')
+
+ if state not in ('up', 'powering_up', 'restoring_state'):
+ self._queue = [ opStart, opFinish ]
+ return self.__executeQueue()
+
+ self.cache().put('ready', '1')
+ return State.FINISHED
+
+ def notifyReadyFromOsManager(self, data):
+ # Here we will check for suspending the VM (when full ready)
+ logger.debug('Checking if cache 2 for {0}'.format(self._name))
+ if self.__getCurrentOp() == opWait:
+ logger.debug('Machine is ready. Moving to level 2')
+ self.__popCurrentOp() # Remove current state
+ return self.__executeQueue()
+ # Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
+ return State.FINISHED
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+ '''
+ logger.debug('Deploying for user')
+ self.__initQueueForDeploy(False)
+ return self.__executeQueue()
+
+ def deployForCache(self, cacheLevel):
+ '''
+ Deploys an service instance for cache
+ '''
+ self.__initQueueForDeploy(cacheLevel == self.L2_CACHE)
+ return self.__executeQueue()
+
+ def __initQueueForDeploy(self, forLevel2 = False):
+
+ if forLevel2 is False:
+ self._queue = [opCreate, opChangeMac, opStart, opFinish]
+ else:
+ self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
+
+ def __checkMachineState(self, chkState):
+ logger.debug('Checking that state of machine {0} is {1}'.format(self._vmid, chkState))
+ state = self.service().getMachineState(self._vmid)
+
+ # If we want to check an state and machine does not exists (except in case that we whant to check this)
+ if state == 'unknown' and chkState != 'unknown':
+ return self.__error('Machine not found')
+
+ ret = State.RUNNING
+ if type(chkState) is list:
+ for cks in chkState:
+ if state == cks:
+ ret = State.FINISHED
+ break
+ else:
+ if state == chkState:
+ ret = State.FINISHED
+
+ return ret
+
+ def __getCurrentOp(self):
+ if len(self._queue) == 0:
+ return opFinish
+
+ return self._queue[0]
+
+ def __popCurrentOp(self):
+ if len(self._queue) == 0:
+ return opFinish
+
+ res = self._queue.pop(0)
+ return res
+
+ def __pushFrontOp(self, op):
+ self._queue.insert(0, op)
+
+ def __pushBackOp(self, op):
+ self._queue.append(op)
+
+ def __error(self, reason):
+ '''
+ Internal method to set object as error state
+
+ Returns:
+ State.ERROR, so we can do "return self.__error(reason)"
+ '''
+ logger.debug('Setting error state, reason: {0}'.format(reason))
+
+ self._queue = [opError]
+ self._reason = str(reason)
+ return State.ERROR
+
+ def __executeQueue(self):
+ self.__debug('executeQueue')
+ op = self.__getCurrentOp()
+
+ if op == opError:
+ return State.ERROR
+
+ if op == opFinish:
+ return State.FINISHED
+
+ fncs = { opCreate: self.__create,
+ opRetry: self.__retry,
+ opStart: self.__startMachine,
+ opStop: self.__stopMachine,
+ opSuspend: self.__suspendMachine,
+ opWait: self.__wait,
+ opRemove: self.__remove,
+ opChangeMac: self.__changeMac
+ }
+
+ try:
+ execFnc = fncs.get(op, None)
+
+ if execFnc is None:
+ return self.__error('Unknown operation found at execution queue ({0})'.format(op))
+
+ execFnc()
+
+ return State.RUNNING
+ except Exception as e:
+ return self.__error(e)
+
+ # Queue execution methods
+ def __retry(self):
+ '''
+ Used to retry an operation
+ In fact, this will not be never invoked, unless we push it twice, because
+ checkState method will "pop" first item when a check operation returns State.FINISHED
+
+ At executeQueue this return value will be ignored, and it will only be used at checkState
+ '''
+ return State.FINISHED
+
+ def __wait(self):
+ '''
+ Executes opWait, it simply waits something "external" to end
+ '''
+ return State.RUNNING
+
+ def __create(self):
+ '''
+ Deploys a machine from template for user/cache
+ '''
+ templateId = self.publication().getTemplateId()
+ name = self.service().sanitizeVmName(self.getName()) # oVirt don't let us to create machines with more than 15 chars!!!
+ comments = 'UDS Linked clone'
+
+ self._vmid = self.service().deployFromTemplate(name, comments, templateId)
+ if self._vmid is None:
+ raise Exception('Can\'t create machine')
+
+ def __remove(self):
+ '''
+ Removes a machine from system
+ '''
+ state = self.service().getMachineState(self._vmid)
+
+ if state == 'unknown':
+ raise Exception('Machine not found')
+
+ if state != 'down':
+ self.__pushFrontOp(opStop)
+ self.__executeQueue()
+ else:
+ self.service().removeMachine(self._vmid)
+
+ def __startMachine(self):
+ '''
+ Powers on the machine
+ '''
+ state = self.service().getMachineState(self._vmid)
+
+ if state == 'unknown':
+ raise Exception('Machine not found')
+
+ if state == 'up': # Already started, return
+ return
+
+ if state != 'down' and state != 'suspended':
+ self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
+ else:
+ self.service().startMachine(self._vmid)
+
+ def __stopMachine(self):
+ '''
+ Powers off the machine
+ '''
+ state = self.service().getMachineState(self._vmid)
+
+ if state == 'unknown':
+ raise Exception('Machine not found')
+
+ if state == 'down': # Already stoped, return
+ return
+
+ if state != 'up' and state != 'suspended':
+ self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
+ else:
+ self.service().stopMachine(self._vmid)
+
+ def __suspendMachine(self):
+ '''
+ Suspends the machine
+ '''
+ state = self.service().getMachineState(self._vmid)
+
+ if state == 'unknown':
+ raise Exception('Machine not found')
+
+ if state == 'suspended': # Already suspended, return
+ return
+
+ if state != 'up':
+ self.__pushFrontOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
+ else:
+ self.service().suspendMachine(self._vmid)
+
+ def __changeMac(self):
+ '''
+ Changes the mac of the first nic
+ '''
+ self.service().updateMachineMac(self._vmid, self.getUniqueId())
+
+ # Check methods
+ def __checkCreate(self):
+ '''
+ Checks the state of a deploy for an user or cache
+ '''
+ return self.__checkMachineState('down')
+
+ def __checkStart(self):
+ '''
+ Checks if machine has started
+ '''
+ return self.__checkMachineState('up')
+
+ def __checkStop(self):
+ '''
+ Checks if machine has stoped
+ '''
+ return self.__checkMachineState('down')
+
+ def __checkSuspend(self):
+ '''
+ Check if the machine has suspended
+ '''
+ return self.__checkMachineState('suspended')
+
+ def __checkRemoved(self):
+ '''
+ Checks if a machine has been removed
+ '''
+ return self.__checkMachineState('unknown')
+
+ def __checkMac(self):
+ '''
+ Checks if change mac operation has finished.
+
+ Changing nic configuration es 1-step operation, so when we check it here, it is already done
+ '''
+ return State.FINISHED
+
+ def checkState(self):
+ '''
+ Check what operation is going on, and acts acordly to it
+ '''
+ self.__debug('checkState')
+ op = self.__getCurrentOp()
+
+ if op == opError:
+ return State.ERROR
+
+ if op == opFinish:
+ return State.FINISHED
+
+ fncs = { opCreate: self.__checkCreate,
+ opRetry: self.__retry,
+ opWait: self.__wait,
+ opStart: self.__checkStart,
+ opStop: self.__checkStop,
+ opSuspend: self.__checkSuspend,
+ opRemove: self.__checkRemoved,
+ opChangeMac: self.__checkMac
+ }
+
+ try:
+ chkFnc = fncs.get(op, None)
+
+ if chkFnc is None:
+ return self.__error('Unknown operation found at check queue ({0})'.format(op))
+
+ state = chkFnc()
+ if state == State.FINISHED:
+ self.__popCurrentOp() # Remove runing op
+ return self.__executeQueue()
+
+ return state
+ except Exception as e:
+ return self.__error(e)
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter wether it is for cache or for an user)
+ '''
+ self.__debug('finish')
+ pass
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This gives the User Deployment an oportunity to do whatever actions
+ are required so the service puts at a correct state for using by a service.
+ '''
+ pass
+
+ def moveToCache(self, newLevel):
+ '''
+ Moves machines between cache levels
+ '''
+ if opRemove in self._queue:
+ return State.RUNNING
+
+ if newLevel == self.L1_CACHE:
+ self._queue = [opStart, opFinish]
+ else:
+ self._queue = [opStart, opSuspend, opFinish]
+
+ return self.__executeQueue()
+
+ def userLoggedIn(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We store the value at storage, but never get used, just an example
+ pass
+
+ def userLoggedOut(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ pass
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+ '''
+ return self._reason
+
+ def destroy(self):
+ '''
+ Invoked for destroying a deployed service
+ '''
+ self.__debug('destroy')
+ # If executing something, wait until finished to remove it
+ # We simply replace the execution queue
+ op = self.__getCurrentOp()
+
+ if op == opError:
+ return self.__error('Machine is already in error state!')
+
+ if op == opFinish or op == opWait:
+ self._queue = [opStop, opRemove, opFinish]
+ return self.__executeQueue()
+
+ self._queue = [op, opStop, opRemove, opFinish]
+ # Do not execute anything.here, just continue normally
+ return State.RUNNING
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+ '''
+ return self.destroy()
+
+
+ @staticmethod
+ def __op2str(op):
+ return { opCreate: 'create',
+ opStart: 'start',
+ opStop: 'stop',
+ opSuspend: 'suspend',
+ opRemove: 'remove',
+ opWait: 'wait',
+ opError: 'error',
+ opFinish: 'finish',
+ opRetry: 'retry',
+ opChangeMac: 'changing mac'
+ }.get(op, '????')
+
+ def __debug(self, txt):
+ logger.debug('_name {0}: {1}'.format(txt, self._name))
+ logger.debug('_ip {0}: {1}'.format(txt, self._ip))
+ logger.debug('_mac {0}: {1}'.format(txt, self._mac))
+ logger.debug('_vmid {0}: {1}'.format(txt, self._vmid))
+ logger.debug('Queue at {0}: {1}'.format(txt,[OVirtLinkedDeployment.__op2str(op) for op in self._queue ]))
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/services/OVirt/OVirtLinkedService.py b/trunk/server/src/uds/services/OVirt/OVirtLinkedService.py
new file mode 100644
index 00000000..3a010c84
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/OVirtLinkedService.py
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _, ugettext
+from uds.core.services import Service
+from OVirtPublication import OVirtPublication
+from OVirtLinkedDeployment import OVirtLinkedDeployment
+
+from uds.core.ui import gui
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+from Helpers import oVirtHelpers
+
+class OVirtLinkedService(Service):
+ '''
+ oVirt Linked clones service. This is based on creating a template from selected vm, and then use it to
+
+
+ '''
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as _ (using ugettext_noop)
+ typeName = _('oVirt Linked Clone (Experimental)')
+ #: Type used internally to identify this provider
+ typeType = 'oVirtLinkedService'
+ #: Description shown at administration interface for this provider
+ typeDescription = _('oVirt Services based on templates and COW (experimental)')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as _ (using ugettext_noop)
+ iconFile = 'service.png'
+
+ # Functional related data
+
+ #: If the service provides more than 1 "deployed user" (-1 = no limit,
+ #: 0 = ???? (do not use it!!!), N = max number to deploy
+ maxDeployed = -1
+ #: If we need to generate "cache" for this service, so users can access the
+ #: provided services faster. Is usesCache is True, you will need also
+ #: set publicationType, do take care about that!
+ usesCache = True
+ #: Tooltip shown to user when this item is pointed at admin interface, none
+ #: because we don't use it
+ cacheTooltip = _('Number of desired machines to keep running waiting for a user')
+ #: If we need to generate a "Level 2" cache for this service (i.e., L1
+ #: could be running machines and L2 suspended machines)
+ usesCache_L2 = True
+ #: Tooltip shown to user when this item is pointed at admin interface, None
+ #: also because we don't use it
+ cacheTooltip_L2 = _('Number of desired machines to keep suspended waiting for use')
+
+ #: If the service needs a s.o. manager (managers are related to agents
+ #: provided by services itselfs, i.e. virtual machines with actors)
+ needsManager = True
+ #: If true, the system can't do an automatic assignation of a deployed user
+ #: service from this service
+ mustAssignManually = False
+
+ #: Types of publications (preparated data for deploys)
+ #: In our case, we do no need a publication, so this is None
+ publicationType = OVirtPublication
+ #: Types of deploys (services in cache and/or assigned to users)
+ deployedType = OVirtLinkedDeployment
+
+ # Now the form part
+ machine = gui.ChoiceField(label=_("Base Machine"), order = 1, tooltip = _('Service base machine'), required = True)
+ cluster = gui.ChoiceField(label=_("Cluster"), order = 2,
+ fills = {
+ 'callbackName' : 'ovFillResourcesFromCluster',
+ 'function' : oVirtHelpers.getResources,
+ 'parameters' : ['cluster', 'ov', 'ev']
+ },
+ tooltip = _("Cluster to contain services"), required = True
+ )
+
+ datastore = gui.ChoiceField(label = _("Datastore Domain"), rdonly = False, order = 3,
+ tooltip = _('Datastore domain where to publish and put incrementals'), required = True)
+
+ memory = gui.NumericField(label = _("Memory (Mb)"), length = 4, defvalue = 512, rdonly = False, order = 4,
+ tooltip = _('Memory assigned to machines'), required = True)
+
+ memoryGuaranteed = gui.NumericField(label = _("Memory Guaranteed (Mb)"), length = 4, defvalue = 256, rdonly = False, order = 5,
+ tooltip = _('Physical memory guaranteed to machines'), required = True)
+
+ baseName = gui.TextField(label = _('Machine Names'), rdonly = False, order = 6, tooltip = ('Base name for clones from this machine'), required = True)
+ lenName = gui.NumericField(length = 1, label = _('Name Length'), defvalue = 5, order = 7,
+ tooltip = _('Length of numeric part for the names of this machines (betwen 3 and 6'), required = True)
+
+ display = gui.ChoiceField(label = _('Display'), rdonly = False, order = 8,
+ tooltip = _('Display type (only for administration pourposses)'),
+ values = [ gui.choiceItem('spice', 'Spice'),
+ gui.choiceItem('vnc', 'Vnc')
+ ],
+ defvalue = '1' # Default value is the ID of the choicefield
+ )
+
+ ov = gui.HiddenField()
+ ev = gui.HiddenField() # We need to keep the env so we can instantiate the Provider
+
+ def initialize(self, values):
+ '''
+ We check here form values to see if they are valid.
+
+ Note that we check them throught FROM variables, that already has been
+ initialized by __init__ method of base class, before invoking this.
+ '''
+ if values is not None:
+ length = int(self.lenName.value)
+ if len(self.baseName.value) + length > 15:
+ raise Service.ValidationException(_('The length of basename plus length must not be greater than 15'))
+ if self.baseName.value.isdigit():
+ raise Service.ValidationException(_('The machine name can\'t be only numbers'))
+
+ def initGui(self):
+ '''
+ Loads required values inside
+ '''
+
+ # Here we have to use "default values", cause values aren't used at form initialization
+ # This is that value is always '', so if we want to change something, we have to do it
+ # at defValue
+ self.ov.defValue = self.parent().serialize()
+ self.ev.defValue = self.parent().env().key()
+
+ machines = self.parent().getMachines()
+ vals = []
+ for m in machines:
+ vals.append( gui.choiceItem( m['id'], m['name'] ))
+
+ # This is not the same case, values is not the "value" of the field, but
+ # the list of values shown because this is a "ChoiceField"
+ self.machine.setValues(vals)
+
+ clusters = self.parent().getClusters()
+ vals = []
+ for c in clusters:
+ vals.append( gui.choiceItem(c['id'], c['name'] ) )
+ self.cluster.setValues(vals)
+
+ def sanitizeVmName(self, name):
+ '''
+ Ovirt only allows machine names with [a-zA-Z0-9_-]
+ '''
+ import re
+ return re.sub("[^a-zA-Z0-9_-]", "_", name)
+
+ def makeTemplate(self, name, comments):
+ '''
+ Invokes makeTemplate from parent provider, completing params
+
+ Args:
+ name: Name to assign to template (must be previously "sanitized"
+ comments: Comments (UTF-8) to add to template
+
+ Returns:
+ template Id of the template created
+
+ Raises an exception if operation fails.
+ '''
+ return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value, self.display.value)
+
+ def getTemplateState(self, templateId):
+ '''
+ Invokes getTemplateState from parent provider
+
+ Args:
+ templateId: templateId to remove
+
+ Returns nothing
+
+ Raises an exception if operation fails.
+ '''
+ return self.parent().getTemplateState(templateId)
+
+ def deployFromTemplate(self, name, comments, templateId):
+ '''
+ Deploys a virtual machine on selected cluster from selected template
+
+ Args:
+ name: Name (sanitized) of the machine
+ comments: Comments for machine
+ templateId: Id of the template to deploy from
+ displayType: 'vnc' or 'spice'. Display to use ad oVirt admin interface
+ memoryMB: Memory requested for machine, in MB
+ guaranteedMB: Minimum memory guaranteed for this machine
+
+ Returns:
+ Id of the machine being created form template
+ '''
+ logger.debug('Deploying from template {0} machine {1}'.format(templateId, name))
+ return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
+ self.display.value, int(self.memory.value), int(self.memoryGuaranteed.value))
+
+ def removeTemplate(self, templateId):
+ '''
+ invokes removeTemplate from parent provider
+ '''
+ return self.parent().removeTemplate(templateId)
+
+ def getMachineState(self, machineId):
+ '''
+ Invokes getMachineState from parent provider
+ (returns if machine is "active" or "inactive"
+
+ Args:
+ machineId: If of the machine to get state
+
+ Returns:
+ one of this values:
+ unassigned, down, up, powering_up, powered_down,
+ paused, migrating_from, migrating_to, unknown, not_responding,
+ wait_for_launch, reboot_in_progress, saving_state, restoring_state,
+ suspended, image_illegal, image_locked or powering_down
+ Also can return'unknown' if Machine is not known
+ '''
+ return self.parent().getMachineState(machineId)
+
+ def startMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt.
+
+ This start also "resume" suspended/paused machines
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.parent().startMachine(machineId)
+
+ def stopMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.parent().stopMachine(machineId)
+
+ def suspendMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.parent().suspendMachine(machineId)
+
+ def removeMachine(self, machineId):
+ '''
+ Tries to delete a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.parent().removeMachine(machineId)
+
+ def updateMachineMac(self, machineId, macAddres):
+ '''
+ Changes the mac address of first nic of the machine to the one specified
+ '''
+ return self.parent().updateMachineMac(machineId, macAddres)
+
+ def getMacRange(self):
+ '''
+ Returns de selected mac range
+ '''
+ return self.parent().getMacRange()
+
+ def getBaseName(self):
+ '''
+ Returns the base name
+ '''
+ return self.baseName.value
+
+ def getLenName(self):
+ '''
+ Returns the length of numbers part
+ '''
+ return int(self.lenName.value)
+
+ def getDisplay(self):
+ '''
+ Returns the selected display type (for created machines, for administration
+ '''
+ return self.display.value
+
diff --git a/trunk/server/src/uds/services/OVirt/OVirtProvider.py b/trunk/server/src/uds/services/OVirt/OVirtProvider.py
new file mode 100644
index 00000000..a8c6146f
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/OVirtProvider.py
@@ -0,0 +1,404 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Created on Jun 22, 2012
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as _, ugettext
+from uds.core.util.State import State
+from uds.core.services import ServiceProvider
+from OVirtLinkedService import OVirtLinkedService
+from uds.core.ui import gui
+
+from client import oVirtClient
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+CACHE_TIME_FOR_SERVER = 1800
+
+class Provider(ServiceProvider):
+ '''
+ This class represents the sample services provider
+
+ In this class we provide:
+ * The Provider functionality
+ * The basic configuration parameters for the provider
+ * The form fields needed by administrators to configure this provider
+
+ :note: At class level, the translation must be simply marked as so
+ using ugettext_noop. This is so cause we will translate the string when
+ sent to the administration client.
+
+ For this class to get visible at administration client as a provider type,
+ we MUST register it at package __init__.
+
+ '''
+ #: What kind of services we offer, this are classes inherited from Service
+ offers = [OVirtLinkedService]
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as _ (using ugettext_noop)
+ typeName = _('oVirt Platform Provider')
+ #: Type used internally to identify this provider
+ typeType = 'oVirtPlatform'
+ #: Description shown at administration interface for this provider
+ typeDescription = _('oVirt platform service provider')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as _ (using ugettext_noop)
+ iconFile = 'provider.png'
+
+ # now comes the form fields
+ # There is always two fields that are requested to the admin, that are:
+ # Service Name, that is a name that the admin uses to name this provider
+ # Description, that is a short description that the admin gives to this provider
+ # Now we are going to add a few fields that we need to use this provider
+ # Remember that these are "dummy" fields, that in fact are not required
+ # but used for sample purposes
+ # If we don't indicate an order, the output order of fields will be
+ # "random"
+ host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('oVirt Server IP or Hostname'), required = True)
+ username = gui.TextField(length=32, label = _('Username'), order = 3, tooltip = _('User with valid privileges on oVirt, (use "user@domain" form)'), required = True, defvalue='admin@internal')
+ password = gui.PasswordField(lenth=32, label = _('Password'), order = 4, tooltip = _('Password of the user of oVirt'), required = True)
+ timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 5, tooltip = _('Timeout in seconds of connection to VC'), required = True)
+ macsRange = gui.TextField(length=36, label = _('Macs range'), defvalue = '52:54:00:00:00:00-52:54:00:FF:FF:FF', order = 6, rdonly = True,
+ tooltip = _('Range of valids macs for created machines'), required = True)
+
+
+
+ # oVirt engine, right now, only permits a connection to one server and only one per instance
+ # If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
+ # We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
+ # and this way all will be fine
+ def __getApi(self):
+ '''
+ Returns the connection API object for oVirt (using ovirtsdk)
+ '''
+ if self._api is None:
+ self._api = oVirtClient.Client(self.host.value, self.username.value, self.password.value, self.timeout.value, self.cache())
+ return self._api
+
+ # There is more fields type, but not here the best place to cover it
+ def initialize(self, values = None):
+ '''
+ We will use the "autosave" feature for form fields
+ '''
+
+ # Just reset _api connection variable
+ self._api = None
+
+ def testConnection(self):
+ '''
+ Test that conection to oVirt server is fine
+
+ Returns
+
+ True if all went fine, false if id didn't
+ '''
+
+ return self.__getApi().test()
+
+ def getMachines(self, force = False):
+ '''
+ Obtains the list of machines inside oVirt.
+ Machines starting with UDS are filtered out
+
+ Args:
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+ An array of dictionaries, containing:
+ 'name'
+ 'id'
+ 'cluster_id'
+ '''
+
+ return self.__getApi().getVms(force)
+
+ def getClusters(self, force = False):
+ '''
+ Obtains the list of clusters inside oVirt.
+
+ Args:
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+ Filters out clusters not attached to any datacenter
+ An array of dictionaries, containing:
+ 'name'
+ 'id'
+ 'datacenter_id'
+ '''
+
+ return self.__getApi().getClusters(force)
+
+ def getClusterInfo(self, clusterId, force = False):
+ '''
+ Obtains the cluster info
+
+ Args:
+ datacenterId: Id of the cluster to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'name'
+ 'id'
+ 'datacenter_id'
+ '''
+ return self.__getApi().getClusterInfo(clusterId, force)
+
+ def getDatacenterInfo(self, datacenterId, force = False):
+ '''
+ Obtains the datacenter info
+
+ Args:
+ datacenterId: Id of the datacenter to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'name'
+ 'id'
+ 'storage_type' -> ('isisi', 'nfs', ....)
+ 'storage_format' -> ('v1', v2')
+ 'description'
+ 'storage' -> array of dictionaries, with:
+ 'id' -> Storage id
+ 'name' -> Storage name
+ 'type' -> Storage type ('data', 'iso')
+ 'available' -> Space available, in bytes
+ 'used' -> Space used, in bytes
+ 'active' -> True or False
+
+ '''
+ return self.__getApi().getDatacenterInfo(datacenterId, force)
+
+ def getStorageInfo(self, storageId, force = False):
+ '''
+ Obtains the datacenter info
+
+ Args:
+ datacenterId: Id of the datacenter to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'id' -> Storage id
+ 'name' -> Storage name
+ 'type' -> Storage type ('data', 'iso')
+ 'available' -> Space available, in bytes
+ 'used' -> Space used, in bytes
+ # 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)
+
+ '''
+ return self.__getApi().getStorageInfo(storageId, force)
+
+ def makeTemplate(self, name, comments, machineId, clusterId, storageId, displayType):
+ '''
+ Publish the machine (makes a template from it so we can create COWs) and returns the template id of
+ the creating machine
+
+ Args:
+ name: Name of the machine (care, only ascii characters and no spaces!!!)
+ machineId: id of the machine to be published
+ clusterId: id of the cluster that will hold the machine
+ storageId: id of the storage tuat will contain the publication AND linked clones
+ displayType: type of display (for oVirt admin interface only)
+
+ Returns
+ Raises an exception if operation could not be acomplished, or returns the id of the template being created.
+ '''
+ return self.__getApi().makeTemplate(name, comments, machineId, clusterId, storageId, displayType)
+
+ def getTemplateState(self, templateId):
+ '''
+ Returns current template state.
+
+ Returned values could be:
+ ok
+ locked
+ removed
+
+ (don't know if ovirt returns something more right now, will test what happens when template can't be published)
+ '''
+ return self.__getApi().getTemplateState(templateId)
+
+ def getMachineState(self, machineId):
+ '''
+ Returns the state of the machine
+ This method do not uses cache at all (it always tries to get machine state from oVirt server)
+
+ Args:
+ machineId: Id of the machine to get state
+
+ Returns:
+ one of this values:
+ unassigned, down, up, powering_up, powered_down,
+ paused, migrating_from, migrating_to, unknown, not_responding,
+ wait_for_launch, reboot_in_progress, saving_state, restoring_state,
+ suspended, image_illegal, image_locked or powering_down
+ Also can return'unknown' if Machine is not known
+ '''
+ return self.__getApi().getMachineState(machineId)
+
+ return State.INACTIVE
+
+ def removeTemplate(self, templateId):
+ '''
+ Removes a template from ovirt server
+
+ Returns nothing, and raises an Exception if it fails
+ '''
+ return self.__getApi().removeTemplate(templateId)
+
+ def deployFromTemplate(self, name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB):
+ '''
+ Deploys a virtual machine on selected cluster from selected template
+
+ Args:
+ name: Name (sanitized) of the machine
+ comments: Comments for machine
+ templateId: Id of the template to deploy from
+ clusterId: Id of the cluster to deploy to
+ displayType: 'vnc' or 'spice'. Display to use ad oVirt admin interface
+ memoryMB: Memory requested for machine, in MB
+ guaranteedMB: Minimum memory guaranteed for this machine
+
+ Returns:
+ Id of the machine being created form template
+ '''
+ return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB)
+
+ def startMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt.
+
+ This start also "resume" suspended/paused machines
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.__getApi().startMachine(machineId)
+
+ def stopMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.__getApi().stopMachine(machineId)
+
+ def suspendMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.__getApi().suspendMachine(machineId)
+
+ def removeMachine(self, machineId):
+ '''
+ Tries to delete a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ return self.__getApi().removeMachine(machineId)
+
+ def updateMachineMac(self, machineId, macAddres):
+ '''
+ Changes the mac address of first nic of the machine to the one specified
+ '''
+ return self.__getApi().updateMachineMac(machineId, macAddres)
+
+ def getMacRange(self):
+ return self.macsRange.value
+
+ @staticmethod
+ def test(env, data):
+ '''
+ Test ovirt Connectivity
+
+ Args:
+ env: environment passed for testing (temporal environment passed)
+
+ data: data passed for testing (data obtained from the form
+ definition)
+
+ Returns:
+ Array of two elements, first is True of False, depending on test
+ (True is all right, false is error),
+ second is an String with error, preferably internacionalizated..
+
+ '''
+ #try:
+ # # We instantiate the provider, but this may fail...
+ # instance = Provider(env, data)
+ # logger.debug('Methuselah has {0} years and is {1} :-)'
+ # .format(instance.methAge.value, instance.methAlive.value))
+ #except ServiceProvider.ValidationException as e:
+ # # If we say that meth is alive, instantiation will
+ # return [False, str(e)]
+ #except Exception as e:
+ # logger.exception("Exception caugth!!!")
+ # return [False, str(e)]
+ #return [True, _('Nothing tested, but all went fine..')]
+ ov = Provider(env, data)
+ if ov.testConnection() is True:
+ return [True, _('Connection test successful')]
+ return [False, _("Connection failed. Check connection params")]
+
+
+
+
+
diff --git a/trunk/server/src/uds/services/OVirt/OVirtPublication.py b/trunk/server/src/uds/services/OVirt/OVirtPublication.py
new file mode 100644
index 00000000..cee80709
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/OVirtPublication.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.services import Publication
+from uds.core.util.State import State
+from datetime import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+class OVirtPublication(Publication):
+ '''
+ This class provides the publication of a oVirtLinkedService
+ '''
+
+ suggestedTime = 20 #: Suggested recheck time if publication is unfinished in seconds
+
+ def initialize(self):
+ '''
+ This method will be invoked by default __init__ of base class, so it gives
+ us the oportunity to initialize whataver we need here.
+
+ In our case, we setup a few attributes..
+ '''
+
+ # We do not check anything at marshal method, so we ensure that
+ # default values are correctly handled by marshal.
+ self._name = ''
+ self._reason = ''
+ self._destroyAfter = 'f'
+ self._templateId = ''
+ self._state = 'r'
+
+ def marshal(self):
+ '''
+ returns data from an instance of Sample Publication serialized
+ '''
+ return '\t'.join( ['v1', self._name, self._reason, self._destroyAfter, self._templateId, self._state] )
+
+ def unmarshal(self, data):
+ '''
+ deserializes the data and loads it inside instance.
+ '''
+ logger.debug('Data: {0}'.format(data))
+ vals = data.split('\t')
+ if vals[0] == 'v1':
+ self._name, self._reason, self._destroyAfter, self._templateId, self._state = vals[1:]
+
+ def publish(self):
+ '''
+ Realizes the publication of the service
+ '''
+ self._name = self.service().sanitizeVmName('UDS Publication' + ' ' + self.dsName() + "-" + str(self.revision()))
+ comments = _('UDS pub for {0} at {1}').format( self.dsName(), str(datetime.now()).split('.')[0] )
+ self._reason = '' # No error, no reason for it
+ self._destroyAfter = 'f'
+ self._state = 'locked'
+
+ try:
+ self._templateId = self.service().makeTemplate(self._name, comments)
+ except Exception as e:
+ self._reason = str(e)
+ return State.ERROR
+
+ return State.RUNNING
+
+ def checkState(self):
+ '''
+ Checks state of publication creation
+ '''
+ if self._state == 'ok':
+ return State.FINISHED
+
+ if self._state == 'error':
+ return State.ERROR
+
+ self._state = self.service().getTemplateState(self._templateId)
+
+ # If publication os done (template is ready), and cancel was requested, do it just after template becomes ready
+ if self._state == 'ok':
+ if self._destroyAfter == 't':
+ return self.destroy()
+ return State.FINISHED
+
+ return State.RUNNING
+
+
+ def finish(self):
+ '''
+ In our case, finish does nothing
+ '''
+ pass
+
+ def reasonOfError(self):
+ '''
+ If a publication produces an error, here we must notify the reason why
+ it happened. This will be called just after publish or checkState
+ if they return State.ERROR
+
+ Returns an string, in our case, set at checkState
+ '''
+ return self._reason
+
+ def destroy(self):
+ '''
+ This is called once a publication is no more needed.
+
+ This method do whatever needed to clean up things, such as
+ removing created "external" data (environment gets cleaned by core),
+ etc..
+
+ The retunred value is the same as when publishing, State.RUNNING,
+ State.FINISHED or State.ERROR.
+ '''
+ # We do not do anything else to destroy this instance of publication
+ if self._state == 'locked':
+ self._destroyAfter = 't'
+ return State.RUNNING
+
+ try:
+ self.service().removeTemplate(self._templateId)
+ except Exception as e:
+ self._reason = str(e)
+ return State.ERROR
+
+ return State.FINISHED
+
+ def cancel(self):
+ '''
+ Do same thing as destroy
+ '''
+ return self.destroy()
+
+ # Here ends the publication needed methods.
+ # Methods provided below are specific for this publication
+ # and will be used by user deployments that uses this kind of publication
+
+ def getTemplateId(self):
+ '''
+ Returns the template id associated with the publication
+ '''
+ return self._templateId
diff --git a/trunk/server/src/uds/services/OVirt/__init__.py b/trunk/server/src/uds/services/OVirt/__init__.py
new file mode 100644
index 00000000..9932ca49
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Sample Service module.
+
+This package simply shows how a new service can be implemented.
+
+
+The first thing to do in every package that is a module is register the
+class that is responsible of providing the module with the system.
+
+For this, we must simply import the class at __init__, UDS will take care
+of the rest
+'''
+
+from OVirtProvider import Provider
+
diff --git a/trunk/server/src/uds/services/OVirt/client/__init__.py b/trunk/server/src/uds/services/OVirt/client/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/trunk/server/src/uds/services/OVirt/client/oVirtClient.py b/trunk/server/src/uds/services/OVirt/client/oVirtClient.py
new file mode 100644
index 00000000..6162b3a4
--- /dev/null
+++ b/trunk/server/src/uds/services/OVirt/client/oVirtClient.py
@@ -0,0 +1,625 @@
+'''
+Created on Nov 14, 2012
+
+@author: dkmaster
+'''
+
+from ovirtsdk.xml import params
+from ovirtsdk.api import API
+
+import threading
+import logging
+
+logger = logging.getLogger(__name__)
+
+lock = threading.Lock()
+
+cached_api = None
+cached_api_key = None
+
+class Client(object):
+ '''
+ Module to manage oVirt connections using ovirtsdk.
+
+ Due to the fact that we can't create two proxy connections at same time, we serialize all access to ovirt platform.
+ Only one request and one live connection can exists at a time.
+
+ This can waste a lot of time, so use of cache here is more than important to achieve aceptable performance.
+
+ '''
+
+ CACHE_TIME_LOW = 60*5 # Cache time for requests are 5 minutes by default
+ CACHE_TIME_HIGH = 60*30 # Cache time for requests that are less probable to change (as cluster perteinance of a machine)
+
+ def __getKey(self, prefix = ''):
+ '''
+ Creates a key for the cache, using the prefix indicated as part of it
+
+ Returns:
+ The cache key, taking into consideration the prefix
+ '''
+ return prefix + self._host + self._username + self._password + str(self._timeout)
+
+ def __getApi(self):
+ '''
+ Gets the api connection.
+
+ Again, due to the fact that ovirtsdk don't allow (at this moment, but it's on the "TODO" list) concurrent access to
+ more than one server, we keep only one opened connection.
+
+ Must be acceses "locked", we can alter cached_api and cached_api_key
+ '''
+ global cached_api, cached_api_key
+ aKey = self.__getKey('o-host')
+ if cached_api_key == aKey:
+ return cached_api
+
+ if cached_api is not None:
+ try:
+ cached_api.disconnect()
+ except:
+ # Nothing happens, may it was already disconnected
+ pass
+ try:
+ cached_api_key = aKey
+ cached_api = API(url='https://'+self._host, username=self._username, password=self._password, timeout=self._timeout, insecure=True, debug=True)
+ return cached_api
+ except:
+ cached_api_key = None
+ raise Exception("Can't connet to server at {0}".format(self._host))
+
+
+ def __init__(self, host, username, password, timeout, cache):
+ self._host = host
+ self._username = username
+ self._password = password
+ self._timeout = int(timeout)
+ self._cache = cache
+
+
+ def test(self):
+ try:
+ lock.acquire(True)
+ return self.__getApi().test()
+ except Exception as e:
+ print e
+ return False
+ finally:
+ lock.release()
+
+ def getVms(self, force = False):
+ '''
+ Obtains the list of machines inside ovirt that do aren't part of uds
+
+ Args:
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+ An array of dictionaries, containing:
+ 'name'
+ 'id'
+ 'cluster_id'
+
+ '''
+ vmsKey = self.__getKey('o-vms')
+ val = self._cache.get(vmsKey)
+
+ if val is not None and force is False:
+ return val
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vms = api.vms.list(query='name!=UDS*')
+
+ res = []
+
+ for vm in vms:
+ res.append({ 'name' : vm.get_name(), 'id' : vm.get_id(), 'cluster_id' : vm.get_cluster().get_id() })
+
+ self._cache.put(vmsKey, res, Client.CACHE_TIME_LOW)
+
+ return res
+
+ finally:
+ lock.release()
+
+ def getClusters(self, force = False):
+ '''
+ Obtains the list of clusters inside ovirt
+
+ Args:
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+ Filters out clusters not attached to any datacenter
+ An array of dictionaries, containing:
+ 'name'
+ 'id'
+ 'datacenter_id'
+
+ '''
+ clsKey = self.__getKey('o-clusters')
+ val = self._cache.get(clsKey)
+
+ if val is not None and force is False:
+ return val
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ clusters = api.clusters.list()
+
+ res = []
+
+ for cluster in clusters:
+ dc = cluster.get_data_center()
+
+ if dc is not None:
+ dc = dc.get_id()
+
+ val = { 'name' : cluster.get_name(), 'id' : cluster.get_id(), 'datacenter_id' : dc }
+
+ # Updates cache info for every single cluster
+ clKey = self.__getKey('o-cluster'+cluster.get_id())
+ self._cache.put(clKey, val)
+
+ if dc is not None:
+ res.append(val)
+
+ self._cache.put(clsKey, res, Client.CACHE_TIME_HIGH)
+
+ return res
+
+ finally:
+ lock.release()
+
+
+ def getClusterInfo(self, clusterId, force = False):
+ '''
+ Obtains the cluster info
+
+ Args:
+ datacenterId: Id of the cluster to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'name'
+ 'id'
+ 'datacenter_id'
+ '''
+ clKey = self.__getKey('o-cluster'+clusterId)
+ val = self._cache.get(clKey)
+
+ if val is not None and force is False:
+ return val
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ c = api.clusters.get(id=clusterId)
+
+ dc = c.get_data_center()
+
+ if dc is not None:
+ dc = dc.get_id()
+
+ res = { 'name' : c.get_name(), 'id' : c.get_id(), 'datacenter_id' : dc }
+ self._cache.put(clKey, res, Client.CACHE_TIME_HIGH)
+ return res
+ finally:
+ lock.release()
+
+ def getDatacenterInfo(self, datacenterId, force = False):
+ '''
+ Obtains the datacenter info
+
+ Args:
+ datacenterId: Id of the datacenter to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'name'
+ 'id'
+ 'storage_type' -> ('isisi', 'nfs', ....)
+ 'storage_format' -> ('v1', v2')
+ 'description'
+ 'storage' -> array of dictionaries, with:
+ 'id' -> Storage id
+ 'name' -> Storage name
+ 'type' -> Storage type ('data', 'iso')
+ 'available' -> Space available, in bytes
+ 'used' -> Space used, in bytes
+ 'active' -> True or False
+
+ '''
+ dcKey = self.__getKey('o-dc'+datacenterId)
+ val = self._cache.get(dcKey)
+
+ if val is not None and force is False:
+ return val
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ d = api.datacenters.get(id=datacenterId)
+ storage = []
+ for dd in d.storagedomains.list():
+ try:
+ active = dd.get_status().get_state()
+ except:
+ active = 'inactive'
+
+ storage.append( { 'id' : dd.get_id(), 'name' : dd.get_name(), 'type' : dd.get_type(),
+ 'available' : dd.get_available(), 'used' : dd.get_used(),
+ 'active' : active == 'active' } )
+
+
+ res = { 'name' : d.get_name(), 'id' : d.get_id(), 'storage_type' : d.get_storage_type(),
+ 'storage_format' : d.get_storage_format(), 'description' : d.get_description(),
+ 'storage' : storage }
+
+ self._cache.put(dcKey, res, Client.CACHE_TIME_HIGH)
+ return res
+ finally:
+ lock.release()
+
+ def getStorageInfo(self, storageId, force = False):
+ '''
+ Obtains the datacenter info
+
+ Args:
+ datacenterId: Id of the datacenter to get information about it
+ force: If true, force to update the cache, if false, tries to first
+ get data from cache and, if valid, return this.
+
+ Returns
+
+ A dictionary with following values
+ 'id' -> Storage id
+ 'name' -> Storage name
+ 'type' -> Storage type ('data', 'iso')
+ 'available' -> Space available, in bytes
+ 'used' -> Space used, in bytes
+ # 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)
+
+ '''
+ sdKey = self.__getKey('o-sd'+storageId)
+ val = self._cache.get(sdKey)
+
+ if val is not None and force is False:
+ return val
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ dd = api.storagedomains.get(id=storageId)
+
+
+
+ res = { 'id' : dd.get_id(), 'name' : dd.get_name(), 'type' : dd.get_type(),
+ 'available' : dd.get_available(), 'used' : dd.get_used()
+ }
+
+ self._cache.put(sdKey, res, Client.CACHE_TIME_LOW)
+ return res
+ finally:
+ lock.release()
+
+
+ def makeTemplate(self, name, comments, machineId, clusterId, storageId, displayType):
+ '''
+ Publish the machine (makes a template from it so we can create COWs) and returns the template id of
+ the creating machine
+
+ Args:
+ name: Name of the machine (care, only ascii characters and no spaces!!!)
+ machineId: id of the machine to be published
+ clusterId: id of the cluster that will hold the machine
+ storageId: id of the storage tuat will contain the publication AND linked clones
+ displayType: type of display (for oVirt admin interface only)
+
+ Returns
+ Raises an exception if operation could not be acomplished, or returns the id of the template being created.
+ '''
+ logger.debug("n: {0}, c: {1}, vm: {2}, cl: {3}, st: {3}, dt: {4}".format(name, comments, machineId, clusterId, storageId, displayType))
+
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+
+ cluster = api.clusters.get(id=clusterId)
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ if cluster is None:
+ raise Exception('Cluster not found')
+
+ if vm.get_status().get_state() != 'down':
+ raise Exception('Machine must be in down state to publish it')
+
+ # Create disks description to be created in specified storage domain, one for each disk
+ sd = params.StorageDomains(storage_domain=[params.StorageDomain(id=storageId)])
+
+ dsks = []
+ for dsk in vm.disks.list():
+ dsks.append(params.Disk(id=dsk.get_id(), storage_domains=sd))
+
+ disks = params.Disks(disk=dsks)
+
+ # Create display description
+ display = params.Display(type_=displayType)
+
+ template = params.Template(name=name, vm=params.VM(id=vm.get_id(), disks=disks),
+ cluster=params.Cluster(id=cluster.get_id()), description=comments,
+ display=display)
+
+ return api.templates.add(template).get_id()
+
+ #return api.templates.get(name=name).get_id()
+ finally:
+ lock.release()
+
+
+ def getTemplateState(self, templateId):
+ '''
+ Returns current template state.
+ This method do not uses cache at all (it always tries to get template state from oVirt server)
+
+ Returned values could be:
+ ok
+ locked
+ removed
+
+ (don't know if ovirt returns something more right now, will test what happens when template can't be published)
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ template = api.templates.get(id=templateId)
+
+ if template is None:
+ return 'removed'
+
+ return template.get_status().get_state()
+
+ finally:
+ lock.release()
+
+ def deployFromTemplate(self, name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB):
+ '''
+ Deploys a virtual machine on selected cluster from selected template
+
+ Args:
+ name: Name (sanitized) of the machine
+ comments: Comments for machine
+ templateId: Id of the template to deploy from
+ clusterId: Id of the cluster to deploy to
+ displayType: 'vnc' or 'spice'. Display to use ad oVirt admin interface
+ memoryMB: Memory requested for machine, in MB
+ guaranteedMB: Minimum memory guaranteed for this machine
+
+ Returns:
+ Id of the machine being created form template
+ '''
+ logger.debug('Deploying machine with name "{0}" from template {1} at cluster {2} with display {3}, memory {4} and guaranteed {5}'.format(
+ name, templateId, clusterId, displayType, memoryMB, guaranteedMB))
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ logger.debug('Deploying machine {0}'.format(name))
+
+ cluster = params.Cluster(id=clusterId)
+ template = params.Template(id=templateId)
+ display = params.Display(type_=displayType)
+
+ memoryPolicy = params.MemoryPolicy(guaranteed=guaranteedMB*1024*1024)
+ par = params.VM(name=name, cluster=cluster, template=template, description=comments,
+ display=display, type_='desktop', memory=memoryMB*1024*1024, memory_policy=memoryPolicy)
+
+ return api.vms.add(par).get_id()
+
+ finally:
+ lock.release()
+
+ def removeTemplate(self, templateId):
+ '''
+ Removes a template from ovirt server
+
+ Returns nothing, and raises an Exception if it fails
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ template = api.templates.get(id=templateId)
+ if template is None:
+ raise Exception('Template does not exists')
+
+ template.delete()
+ # This returns nothing, if it fails it raises an exception
+ finally:
+ lock.release()
+
+ def getMachineState(self, machineId):
+ '''
+ Returns current state of a machine (running, suspended, ...).
+ This method do not uses cache at all (it always tries to get machine state from oVirt server)
+
+ Args:
+ machineId: Id of the machine to get status
+
+ Returns:
+ one of this values:
+ unassigned, down, up, powering_up, powered_down,
+ paused, migrating_from, migrating_to, unknown, not_responding,
+ wait_for_launch, reboot_in_progress, saving_state, restoring_state,
+ suspended, image_illegal, image_locked or powering_down
+ Also can return'unknown' if Machine is not known
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None or vm.get_status() is None:
+ return 'unknown'
+
+ return vm.get_status().get_state()
+
+ finally:
+ lock.release()
+
+ def startMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt.
+
+ This start also "resume" suspended/paused machines
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ vm.start()
+
+ finally:
+ lock.release()
+
+ def stopMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ vm.stop()
+
+ finally:
+ lock.release()
+
+ def suspendMachine(self, machineId):
+ '''
+ Tries to start a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ vm.suspend()
+
+ finally:
+ lock.release()
+
+ def removeMachine(self, machineId):
+ '''
+ Tries to delete a machine. No check is done, it is simply requested to oVirt
+
+ Args:
+ machineId: Id of the machine
+
+ Returns:
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ vm.delete()
+
+ finally:
+ lock.release()
+
+ def updateMachineMac(self, machineId, macAddres):
+ '''
+ Changes the mac address of first nic of the machine to the one specified
+ '''
+ try:
+ lock.acquire(True)
+
+ api = self.__getApi()
+
+ vm = api.vms.get(id=machineId)
+
+ if vm is None:
+ raise Exception('Machine not found')
+
+ nic = vm.nics.list()[0] # If has no nic, will raise an exception (IndexError)
+
+ nic.get_mac().set_address(macAddres)
+
+ nic.update() # Updates the nic
+
+ except IndexError:
+ raise Exception('Machine do not have network interfaces!!')
+
+ finally:
+ lock.release()
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/services/OVirt/provider.png b/trunk/server/src/uds/services/OVirt/provider.png
new file mode 100644
index 00000000..2748fbca
Binary files /dev/null and b/trunk/server/src/uds/services/OVirt/provider.png differ
diff --git a/trunk/server/src/uds/services/OVirt/service.png b/trunk/server/src/uds/services/OVirt/service.png
new file mode 100644
index 00000000..fc4282c7
Binary files /dev/null and b/trunk/server/src/uds/services/OVirt/service.png differ
diff --git a/trunk/server/src/uds/services/PhysicalMachines/IPMachineDeployed.py b/trunk/server/src/uds/services/PhysicalMachines/IPMachineDeployed.py
new file mode 100644
index 00000000..16f1fc15
--- /dev/null
+++ b/trunk/server/src/uds/services/PhysicalMachines/IPMachineDeployed.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_lazy as _
+from uds.core import services
+from uds.core.util.State import State
+from uds.core.util.AutoAttributes import AutoAttributes
+import logging
+
+logger = logging.getLogger(__name__)
+
+class IPMachineDeployed(AutoAttributes, services.UserDeployment):
+ suggestedTime = 10
+
+ def __init__(self, environment, **kwargs):
+ AutoAttributes.__init__(self, ip=str, reason=str, state=str)
+ services.UserDeployment.__init__(self,environment, **kwargs)
+ self._state = State.FINISHED
+
+ def setIp(self, ip):
+ logger.debug('Setting IP to %s (ignored)' % ip)
+
+ def getIp(self):
+ return self._ip
+
+ def getName(self):
+ return _("IP ") + self._ip
+
+ def getUniqueId(self):
+ return self._ip
+
+ def setReady(self):
+ self._state = State.FINISHED
+ return self._state
+
+ def __deploy(self):
+ ip = self.service().getUnassignedMachine()
+ if ip is None:
+ self._reason = 'No machines left'
+ self._state = State.ERROR
+ else:
+ self._ip = ip
+ self._state = State.FINISHED
+ return self._state
+
+ def deployForUser(self, user):
+ logger.debug("Starting deploy of {0} for user {0}".format(self._ip, user))
+ return self.__deploy()
+
+ def checkState(self):
+ return self._state
+
+ def finish(self):
+ pass
+
+ def reasonOfError(self):
+ '''
+ If a publication produces an error, here we must notify the reason why it happened. This will be called just after
+ publish or checkPublishingState if they return State.ERROR
+ '''
+ return self._reason
+
+ def cancel(self):
+ return self.destroy()
+
+ def destroy(self):
+ if self._ip != '':
+ self.service().unassignMachine(self._ip)
+ self._state = State.FINISHED
+ return self._state
diff --git a/trunk/server/src/uds/services/PhysicalMachines/IPMachinesService.py b/trunk/server/src/uds/services/PhysicalMachines/IPMachinesService.py
new file mode 100644
index 00000000..828b2865
--- /dev/null
+++ b/trunk/server/src/uds/services/PhysicalMachines/IPMachinesService.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_lazy as _
+from uds.core import services
+from uds.core.util.AutoAttributes import AutoAttributes
+from uds.core.ui.UserInterface import gui
+from IPMachineDeployed import IPMachineDeployed
+import logging, cPickle
+
+logger = logging.getLogger(__name__)
+
+class IPMachinesService(services.Service):
+
+ # Gui
+ ipList = gui.EditableList(label=_('List of IPS'))
+
+ # Description of service
+ typeName = _('Physical machines accesed by ip')
+ typeType = 'IPMachinesService'
+ typeDescription = _('This service provides access to POWERED-ON Machines by ip')
+ iconFile = 'machine.png'
+
+ # Characteristics of service
+ maxDeployed = -1 # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
+ usesCache = False # Cache are running machine awaiting to be assigned
+ usesCache_L2 = False # L2 Cache are running machines in suspended state
+ needsManager = False # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
+ mustAssignManually = False # If true, the system can't do an automatic assignation of a deployed user service from this service
+
+ deployedType = IPMachineDeployed
+
+ def __init__(self, environment, parent, values = None):
+ super(IPMachinesService, self).__init__(environment, parent, values)
+ if values is None:
+ self._ips = []
+ else:
+ self._ips = list(set(values['ipList'])) # Avoid duplicates :-)
+ self._ips.sort()
+
+
+ def valuesDict(self):
+ return { 'ipList' : gui.convertToChoices(self._ips) }
+
+ def marshal(self):
+ self.storage().saveData('ips', cPickle.dumps(self._ips))
+ return 'v1'
+
+ def unmarshal(self, vals):
+ if vals == 'v1':
+ self._ips = cPickle.loads( self.storage().readData('ips') )
+
+
+ def getUnassignedMachine(self):
+ # Search first unassigned machine
+ try:
+ self.storage().lock()
+ for ip in self._ips:
+ if self.storage().readData(ip) == None:
+ self.storage().saveData(ip, ip)
+ return ip
+ return None
+ except Exception:
+ logger.exception("Exception at getUnassignedMachine")
+ return None
+ finally:
+ self.storage().unlock()
+
+ def unassignMachine(self, ip):
+ try:
+ self.storage().lock()
+ self.storage().remove(ip)
+ except Exception:
+ logger.exception("Exception at getUnassignedMachine")
+ finally:
+ self.storage().unlock()
diff --git a/trunk/server/src/uds/services/PhysicalMachines/ServiceProvider.py b/trunk/server/src/uds/services/PhysicalMachines/ServiceProvider.py
new file mode 100644
index 00000000..59f6506d
--- /dev/null
+++ b/trunk/server/src/uds/services/PhysicalMachines/ServiceProvider.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.ui.UserInterface import gui
+from uds.core import services
+
+
+class PhysicalMachinesProvider(services.ServiceProvider):
+ # No extra data needed
+
+ # What services do we offer?
+ offers = []
+ typeName = 'Physical Machines Provider'
+ typeType = 'PhysicalMachinesServiceProvider'
+ typeDescription = 'Provides connection to Virtual Center Services'
+ iconFile = 'provider.png'
+
+ from IPMachinesService import IPMachinesService
+ offers = [IPMachinesService]
+
+ def __init__(self, environment, values = None):
+ '''
+ Initializes the Physical Machines Service Provider
+ @param values: a dictionary with the required values, that are the ones declared for gui
+ '''
+ super(PhysicalMachinesProvider, self).__init__(environment, values)
+
+ def marshal(self):
+ '''
+ Serializes the service provider data so we can store it in database
+ '''
+ return str.join( '\t', [ 'v1' ] )
+
+ def unmarshal(self, str):
+ data = str.split('\t')
+ if data[0] == 'v1':
+ pass
+
+ def __str__(self):
+ return "Physical Machines Provider: " + self.marshal()
diff --git a/trunk/server/src/uds/services/PhysicalMachines/__init__.py b/trunk/server/src/uds/services/PhysicalMachines/__init__.py
new file mode 100644
index 00000000..b9de8ef6
--- /dev/null
+++ b/trunk/server/src/uds/services/PhysicalMachines/__init__.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+@author: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from ServiceProvider import PhysicalMachinesProvider
+
+# Now we use __subclasses__ method to locate Service Providers
+# and register them inside factory
+#ServiceProviderFactory.factory().insert(PhysicalMachinesProvider)
diff --git a/trunk/server/src/uds/services/PhysicalMachines/machine.png b/trunk/server/src/uds/services/PhysicalMachines/machine.png
new file mode 100644
index 00000000..4d89bea6
Binary files /dev/null and b/trunk/server/src/uds/services/PhysicalMachines/machine.png differ
diff --git a/trunk/server/src/uds/services/PhysicalMachines/provider.png b/trunk/server/src/uds/services/PhysicalMachines/provider.png
new file mode 100644
index 00000000..e5655f52
Binary files /dev/null and b/trunk/server/src/uds/services/PhysicalMachines/provider.png differ
diff --git a/trunk/server/src/uds/services/Sample/SampleProvider.py b/trunk/server/src/uds/services/Sample/SampleProvider.py
new file mode 100644
index 00000000..4f38368f
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/SampleProvider.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Created on Jun 22, 2012
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as translatable, ugettext as _
+from uds.core.services import ServiceProvider
+from SampleService import ServiceOne, ServiceTwo
+from uds.core.ui import gui
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class Provider(ServiceProvider):
+ '''
+ This class represents the sample services provider
+
+ In this class we provide:
+ * The Provider functionality
+ * The basic configuration parameters for the provider
+ * The form fields needed by administrators to configure this provider
+
+ :note: At class level, the translation must be simply marked as so
+ using ugettext_noop. This is so cause we will translate the string when
+ sent to the administration client.
+
+ For this class to get visible at administration client as a provider type,
+ we MUST register it at package __init__.
+
+ '''
+ #: What kind of services we offer, this are classes inherited from Service
+ offers = [ServiceOne, ServiceTwo]
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ typeName = translatable('Sample Provider')
+ #: Type used internally to identify this provider
+ typeType = 'SampleProvider'
+ #: Description shown at administration interface for this provider
+ typeDescription = translatable('Sample (and dummy) service provider')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ iconFile = 'provider.png'
+
+ # now comes the form fields
+ # There is always two fields that are requested to the admin, that are:
+ # Service Name, that is a name that the admin uses to name this provider
+ # Description, that is a short description that the admin gives to this provider
+ # Now we are going to add a few fields that we need to use this provider
+ # Remember that these are "dummy" fields, that in fact are not required
+ # but used for sample purposes
+ # If we don't indicate an order, the output order of fields will be
+ # "random"
+
+ #: Remote host. Here core will translate label and tooltip, remember to
+ #: mark them as translatable using ugettext_noop.
+ remoteHost = gui.TextField(oder=1,
+ length = 64,
+ label = translatable('Remote host'),
+ tooltip = translatable('This fields contains a remote host'),
+ required = True,
+ )
+ #: Name of your pet (sample, not really needed :-) )
+ petName = gui.TextField(order=2,
+ length = 32,
+ label = translatable('Your pet\'s name'),
+ tooltip = translatable('If you like, write the name of your pet'),
+ requred = False,
+ defvalue = 'Tux' #: This will not get translated
+ )
+ #: Age of Methuselah (matusalén in spanish)
+ #: in Spain there is a well-known to say that something is very old,
+ #: "Tiene mas años que matusalén"(is older than Methuselah)
+ methAge = gui.NumericField(order = 3,
+ length = 4, # That is, max allowed value is 9999
+ label = translatable('Age of Methuselah'),
+ tooltip = translatable('If you know it, please, tell me!!!'),
+ required = True, #: Numeric fields have always a value, so this not really needed
+ defvalue = '4500'
+ )
+
+ #: Is Methuselah istill alive?
+ methAlive = gui.CheckBoxField(order = 4,
+ label = translatable('Is Methuselah still alive?'),
+ tooltip = translatable('If you fails, this will not get saved :-)'),
+ required = True, #: Also means nothing. Check boxes has always a value
+ defvalue = gui.TRUE #: By default, at new item, check this
+ )
+
+ # There is more fields type, but not here the best place to cover it
+ def initialize(self, values = None):
+ '''
+ We will use the "autosave" feature for form fields, that is more than
+ enought for most providers. (We simply need to store data provided by user
+ and, maybe, initialize some kind of connection with this values).
+
+ Normally provider values are rally used at sevice level, cause we never
+ instantiate nothing except a service from a provider.
+ '''
+
+ # If you say meth is alive, you are wrong!!! (i guess..)
+ # values are only passed from administration client. Internals
+ # instantiations are always empty.
+ if values is not None and self.methAlive.isTrue():
+ raise ServiceProvider.ValidationException(_('Methuselah is not alive!!! :-)'))
+
+ # Marshal and unmarshal are defaults ones, also enought
+
+ # As we use "autosave" fields feature, dictValues is also provided by
+ # base class so we don't have to mess with all those things...
+
+ @staticmethod
+ def test(env, data):
+ '''
+ Create your test method here so the admin can push the "check" button
+ and this gets executed.
+ Args:
+ env: environment passed for testing (temporal environment passed)
+
+ data: data passed for testing (data obtained from the form
+ definition)
+
+ Returns:
+ Array of two elements, first is True of False, depending on test
+ (True is all right, false is error),
+ second is an String with error, preferably internacionalizated..
+
+ In this case, wi well do nothing more that use the provider params
+
+ Note also that this is an static method, that will be invoked using
+ the admin user provided data via administration client, and a temporary
+ environment that will be erased after invoking this method
+ '''
+ try:
+ # We instantiate the provider, but this may fail...
+ instance = Provider(env, data)
+ logger.debug('Methuselah has {0} years and is {1} :-)'
+ .format(instance.methAge.value, instance.methAlive.value))
+ except ServiceProvider.ValidationException as e:
+ # If we say that meth is alive, instantiation will
+ return [False, str(e)]
+ except Exception as e:
+ logger.exception("Exception caugth!!!")
+ return [False, str(e)]
+ return [True, _('Nothing tested, but all went fine..')]
+
+ # Congratulations!!!, the needed part of your first simple provider is done!
+ # Now you can go to administration panel, and check it
+ #
+ # From now onwards, we implement our own methods, that will be used by,
+ # for example, services derived from this provider
+ def host(self):
+ '''
+ Sample method, in fact in this we just return
+ the value of host field, that is an string
+ '''
+ return self.remoteHost.value
+
+
+ def methYears(self):
+ '''
+ Another sample return, it will in fact return the Methuselah years
+ '''
diff --git a/trunk/server/src/uds/services/Sample/SamplePublication.py b/trunk/server/src/uds/services/Sample/SamplePublication.py
new file mode 100644
index 00000000..f9ccfd5b
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/SamplePublication.py
@@ -0,0 +1,272 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext as _
+from uds.core.services import Publication
+from uds.core.util.State import State
+from datetime import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SamplePublication(Publication):
+ '''
+ This class shows how a publication is developed.
+
+ In order to a publication to work correctly, we must provide at least the
+ following methods:
+ * Of course, the __init__
+ * :py:meth:`.publish`
+ * :py:meth:`.checkState`
+ * :py:meth:`.finish`
+
+ Also, of course, methods from :py:class:`uds.core.Serializable.Serializable`
+
+
+ Publication do not have an configuration interface, all data contained
+ inside an instance of a Publication must be serialized if you want them between
+ method calls.
+
+ It's not waranteed that the class will not be serialized/deserialized
+ between methods calls, so, first of all, implement the marshal and umnarshal
+ mehods needed by all serializable classes.
+
+ Also a thing to note is that operations requested to Publications must be
+ *as fast as posible*. The operations executes in a separated thread,
+ and so it cant take a bit more time to execute, but it's recommended that
+ the operations executes as fast as posible, and, if it will take a long time,
+ split operation so we can keep track of state.
+
+ This means that, if we have "slow" operations, we must
+
+ We first of all declares an estimation of how long a publication will take.
+ This value is instance based, so if we override it in our class, the suggested
+ time could change.
+
+ The class attribute that indicates this suggested time is "suggestedTime", and
+ it's expressed in seconds, (i.e. "suggestedTime = 10")
+ '''
+
+ suggestedTime = 5 #: Suggested recheck time if publication is unfinished in seconds
+
+ def initialize(self):
+ '''
+ This method will be invoked by default __init__ of base class, so it gives
+ us the oportunity to initialize whataver we need here.
+
+ In our case, we setup a few attributes..
+ '''
+
+ # We do not check anything at marshal method, so we ensure that
+ # default values are correctly handled by marshal.
+ self._name = 'test'
+ self._reason = '' # No error, no reason for it
+ self._number = 1
+
+ def marshal(self):
+ '''
+ returns data from an instance of Sample Publication serialized
+ '''
+ return '\t'.join( [self._name, self._reason, str(self._number)] )
+
+ def unmarshal(self, data):
+ '''
+ deserializes the data and loads it inside instance.
+ '''
+ logger.debug('Data: {0}'.format(data))
+ vals = data.split('\t')
+ logger.debug('Values: {0}'.format(vals))
+ self._name = vals[0]
+ self._reason = vals[1]
+ self._number = int(vals[2])
+
+
+ def publish(self):
+ '''
+ This method is invoked whenever the administrator requests a new publication.
+
+ The method is not invoked directly (i mean, that the administration request
+ do no makes a call to this method), but a DelayedTask is saved witch will
+ initiate all publication stuff (and, of course, call this method).
+
+ You MUST implement it, so the publication do really something.
+ All publications can be synchronous or asynchronous.
+
+ The main difference between both is that first do whatever needed, (the
+ action must be fast enough to do not block core), returning State.FINISHED.
+
+ The second (asynchronous) are publications that could block the core, so
+ it have to be done in more than one step.
+
+ An example publication could be a copy of a virtual machine, where:
+ * First we invoke the copy operation to virtualization provider
+ * Second, we kept needed values inside instance so we can serialize
+ them whenever requested
+ * Returns an State.RUNNING, indicating the core that the publication
+ has started but has to finish sometime later. (We do no check
+ again the state and keep waiting here, because we will block the
+ core untill this operation is finished).
+
+ In our example wi will simple assign a name, and set number to 5. We
+ will use this number later, to make a "delay" at check if the publication
+ has finished. (see method checkState)
+
+ We also will make this publication an "stepped one", that is, it will not
+ finish at publish call but a later checkState call
+
+ Take care with instantiating threads from here. Whenever a publish returns
+ "State.RUNNING", the core will recheck it later, but not using this instance
+ and maybe that even do not use this server.
+
+ If you want to use threadings or somethin likt it, use DelayedTasks and
+ do not block it. You also musht provide the mechanism to allow those
+ DelayedTask to communicate with the publication.
+
+ One sample could be, for example, to copy a bunch of files, but we know
+ that this copy can take a long time and don't want it to take make it
+ all here, but in a separate task. Now, do you remember that "environment"
+ that is unique for every instance?, well, we can create a delayed task,
+ and pass that environment (owned by this intance) as a mechanism for
+ informing when the task is finished. (We insert at delayed tasks queue
+ an instance, not a class itself, so we can instantiate a class and
+ store it at delayed task queue.
+
+ Also note that, in that case, this class can also acomplish that by simply
+ using the suggestedTime attribute and the checkState method in most cases.
+ '''
+ self._number = 5
+ self._reason = ''
+ return State.RUNNING
+
+ def checkState(self):
+ '''
+ Our publish method will initiate publication, but will not finish it.
+ So in our sample, wi will only check if _number reaches 0, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One publish returns State.RUNNING, this task will get called untill
+ checkState returns State.FINISHED.
+
+ Also, wi will make the publication fail one of every 10 calls to this
+ method.
+
+ Note: Destroying an publication also makes use of this method, so you
+ must keep the info of that you are checking (publishing or destroying...)
+ In our case, destroy is 1-step action so this will no get called while
+ destroying...
+ '''
+ import random
+ self._number -= 1
+ # Serialization will take care of storing self._number
+
+ # One of every 10 calls
+ if random.randint(0, 9) == 9:
+ self._reason = _('Random integer was 9!!! :-)')
+ return State.ERROR
+
+ if self._number <= 0:
+ return State.FINISHED
+ else:
+ return State.RUNNING
+
+
+ def finish(self):
+ '''
+ Invoked when Publication manager noticed that the publication has finished.
+ This give us the oportunity of cleaning up things (as stored vars, etc..),
+ or initialize variables that will be needed in a later phase (by deployed
+ services)
+
+ Returned value, if any, is ignored
+ '''
+ import string
+ import random
+ # Make simply a random string
+ self._name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
+
+ def reasonOfError(self):
+ '''
+ If a publication produces an error, here we must notify the reason why
+ it happened. This will be called just after publish or checkState
+ if they return State.ERROR
+
+ Returns an string, in our case, set at checkState
+ '''
+ return self._reason
+
+ def destroy(self):
+ '''
+ This is called once a publication is no more needed.
+
+ This method do whatever needed to clean up things, such as
+ removing created "external" data (environment gets cleaned by core),
+ etc..
+
+ The retunred value is the same as when publishing, State.RUNNING,
+ State.FINISHED or State.ERROR.
+ '''
+ self._name = ''
+ self._reason = '' # In fact, this is not needed, but cleaning up things... :-)
+
+ # We do not do anything else to destroy this instance of publication
+ return State.FINISHED
+
+
+ def cancel(self):
+ '''
+ Invoked for canceling the current operation.
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+
+ Also, take into account that cancel is the initiation of, maybe, a
+ multiple-step action, so it returns, as publish and destroy does.
+
+ In our case, cancel simply invokes "destroy", that cleans up
+ things and returns that the action has finished in 1 step.
+ '''
+ return self.destroy()
+
+ # Here ends the publication needed methods.
+ # Methods provided below are specific for this publication
+ # and will be used by user deployments that uses this kind of publication
+
+ def getBaseName(self):
+ '''
+ This sample method (just for this sample publication), provides
+ the name generater for this publication. This is just a sample, and
+ this will do the work
+ '''
+ return self._name
diff --git a/trunk/server/src/uds/services/Sample/SampleService.py b/trunk/server/src/uds/services/Sample/SampleService.py
new file mode 100644
index 00000000..e38c96f6
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/SampleService.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from django.utils.translation import ugettext_noop as translatable, ugettext as _
+from uds.core.services import Service
+from SamplePublication import SamplePublication
+from SampleUserDeploymentOne import SampleUserDeploymentOne
+from SampleUserDeploymentTwo import SampleUserDeploymentTwo
+
+from uds.core.ui import gui
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+class ServiceOne(Service):
+ '''
+ Basic service, the first part (variables) include the description of the service.
+
+ Remember to fill all variables needed, but at least you must define:
+ * typeName
+ * typeType
+ * typeDescription
+ * iconFile (defaults to service.png)
+ * publicationType, type of publication in case it needs publication.
+ If this is not provided, core will assume that the service do not
+ needs publishing.
+ * deployedType, type of deployed user service. Do not forget this!!!
+
+ The rest of them can be ommited, but its recommended that you fill all
+ declarations shown in this sample (that in fact, are all)
+
+ This description informs the core what this service really provides,
+ and how this is done. Look at description of class variables for more
+ information.
+
+ '''
+ #: Name to show the administrator. This string will be translated BEFORE
+ #: sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ typeName = translatable('Sample Service One')
+ #: Type used internally to identify this provider
+ typeType = 'SampleService1'
+ #: Description shown at administration interface for this provider
+ typeDescription = translatable('Sample (and dummy) service ONE')
+ #: Icon file used as icon for this provider. This string will be translated
+ #: BEFORE sending it to administration interface, so don't forget to
+ #: mark it as translatable (using ugettext_noop)
+ iconFile = 'service.png'
+
+ # Functional related data
+
+ #: If the service provides more than 1 "deployed user" (-1 = no limit,
+ #: 0 = ???? (do not use it!!!), N = max number to deploy
+ maxDeployed = -1
+ #: If we need to generate "cache" for this service, so users can access the
+ #: provided services faster. Is usesCache is True, you will need also
+ #: set publicationType, do take care about that!
+ usesCache = False
+ #: Tooltip shown to user when this item is pointed at admin interface, none
+ #: because we don't use it
+ cacheTooltip = translatable('None')
+ #: If we need to generate a "Level 2" cache for this service (i.e., L1
+ #: could be running machines and L2 suspended machines)
+ usesCache_L2 = False
+ #: Tooltip shown to user when this item is pointed at admin interface, None
+ #: also because we don't use it
+ cacheTooltip_L2 = translatable('None')
+
+ #: If the service needs a s.o. manager (managers are related to agents
+ #: provided by services itselfs, i.e. virtual machines with actors)
+ needsManager = False
+ #: If true, the system can't do an automatic assignation of a deployed user
+ #: service from this service
+ mustAssignManually = False
+
+ #: Types of publications (preparated data for deploys)
+ #: In our case, we do no need a publication, so this is None
+ publicationType = None
+ #: Types of deploys (services in cache and/or assigned to users)
+ deployedType = SampleUserDeploymentOne
+
+ # Now the form part, this service will have only two "dummy" fields
+ # If we don't indicate an order, the output order of fields will be
+ # "random"
+
+ colour = gui.ChoiceField(order = 1,
+ label = translatable('Colour'),
+ tooltip = translatable('Colour of the field'),
+ # In this case, the choice can have none value selected by default
+ required = True,
+ values = [ gui.choiceItem('red', 'Red'),
+ gui.choiceItem('green', 'Green'),
+ gui.choiceItem('blue', 'Blue'),
+ gui.choiceItem('nonsense', 'Blagenta')
+ ],
+ defvalue = '1' # Default value is the ID of the choicefield
+ )
+
+ passw = gui.PasswordField(order = 2,
+ label = translatable('Password'),
+ tooltip = translatable('Password for testing purposes'),
+ required = True,
+ defvalue = '1234' #: Default password are nonsense?? :-)
+ )
+
+ baseName = gui.TextField(order = 3,
+ label = translatable('Services names'),
+ tooltip = translatable('Base name for this user services'),
+ # In this case, the choice can have none value selected by default
+ required = True,
+ defvalue = '' # Default value is the ID of the choicefield
+ )
+
+ def initialize(self, values):
+ '''
+ We check here form values to see if they are valid.
+
+ Note that we check them throught FROM variables, that already has been
+ initialized by __init__ method of base class, before invoking this.
+ '''
+
+ # We don't need to check anything, bat because this is a sample, we do
+ # As in provider, we receive values only at new Service creation,
+ # so we only need to validate params if values is not None
+ if values is not None:
+ if self.colour.value == 'nonsense':
+ raise Service.ValidationException('The selected colour is invalid!!!')
+
+
+ # Services itself are non testeable right now, so we don't even have
+ # to provide one!!!
+
+
+ # Congratulations!!!, the needed part of your first simple service is done!
+ # Now you can go to administration panel, and check it
+ #
+ # From now onwards, we implement our own methods, that will be used by,
+ # for example, services derived from this provider
+
+ def getColour(self):
+ '''
+ Simply returns colour, for deployed user services.
+
+ Remember that choiceField.value returns the id part of the ChoiceItem
+ '''
+ return self.colour.value
+
+ def getPassw(self):
+ '''
+ Simply returns passwd, for deloyed user services
+ '''
+ return self.passw.value
+
+ def getBaseName(self):
+ '''
+ '''
+ return self.baseName.value
+
+
+
+class ServiceTwo(Service):
+ '''
+ Just a second service, no comments here (almost same that ServiceOne
+ '''
+ typeName = translatable('Sample Service Two')
+ typeType = 'SampleService2'
+ typeDescription = translatable('Sample (and dummy) service ONE+ONE')
+ iconFile = 'provider.png' #: We reuse provider icon here :-)
+
+ # Functional related data
+ maxDeployed = 5
+ usesCache = True
+ cacheTooltip = translatable('L1 cache for dummy elements')
+ usesCache_L2 = True
+ cacheTooltip_L2 = translatable('L2 cache for dummy elements')
+
+ needsManager = False
+ mustAssignManually = False
+
+ #: Types of publications. In this case, we will include a publication
+ #: type for this one
+ #: Note that this is a MUST if you indicate that needPublication
+ publicationType = SamplePublication
+ #: Types of deploys (services in cache and/or assigned to users)
+ deployedType = SampleUserDeploymentTwo
+
+
+ # Gui, we will use here the EditableList field
+ names = gui.EditableList(label=translatable('List of names'))
+
+ def __init__(self, environment, parent, values = None):
+ '''
+ We here can get a HUGE list from client.
+ Right now, this is treated same as other fields, in a near
+ future we will se how to handle this better
+ '''
+ super(ServiceTwo, self).__init__(environment, parent, values)
+
+ # No checks here
+
+ def getNames(self):
+ '''
+ For using at deployed services, really nothing
+ '''
+ return self.names.value
diff --git a/trunk/server/src/uds/services/Sample/SampleUserDeploymentOne.py b/trunk/server/src/uds/services/Sample/SampleUserDeploymentOne.py
new file mode 100644
index 00000000..e49d4a15
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/SampleUserDeploymentOne.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.services import UserDeployment
+from uds.core.util.State import State
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleUserDeploymentOne(UserDeployment):
+ '''
+ This class generates the user consumable elements of the service tree.
+
+ After creating at administration interface an Deployed Service, UDS will
+ create consumable services for users using UserDeployment class as
+ provider of this elements.
+
+
+ At class instantiation, this will receive an environment with"generator",
+ that are classes that provides a way to generate unique items.
+
+ The generators provided right now are 'mac' and 'name'. To get more info
+ about this, look at py:class:`uds.core.util.UniqueMacGenerator.UniqueNameGenerator`
+ and py:class:`uds.core.util.UniqueNameGenerator.UniqueNameGenerator`
+
+ This first sample do not uses cache. To see one with cache, see
+ SampleUserDeploymentTwo. The main difference are the "...Cache".." methods,
+ that here are not needed.
+
+ As sample also of environment storage usage, wi will use here the provider
+ storage to keep all our needed info, leaving marshal and unmarshal (needed
+ by Serializble classes, like this) empty (that is, returns '' first and does
+ nothing the second one)
+
+ Also Remember, if you don't include this class as the deployedType of the
+ SampleServiceOne, or whenever you trie to access a service of SampleServiceOne,
+ you will get an excetion that says that you havent included the deployedType.
+ '''
+
+ #: Recheck every five seconds by default (for task methods)
+ suggestedTime = 5
+
+ # Serializable needed methods
+ def marshal(self):
+ '''
+ Does nothing right here, we will use envoronment storage in this sample
+ '''
+ return ''
+
+ def unmarshal(self, str_):
+ '''
+ Does nothing here also, all data are keeped at environment storage
+ '''
+ pass
+
+
+ def getName(self):
+ '''
+ We override this to return a name to display. Default inplementation
+ (in base class), returns getUniqueIde() value
+ This name will help user to identify elements, and is only used
+ at administration interface.
+
+ We will use here the environment name provided generator to generate
+ a name for this element.
+
+ The namaGenerator need two params, the base name and a length for a
+ numeric incremental part for generating unique names. This are unique for
+ all UDS names generations, that is, UDS will not generate this name again
+ until this name is freed, or object is removed, what makes its environment
+ to also get removed, that makes all uniques ids (names and macs right now)
+ to also get released.
+
+ Every time get method of a generator gets called, the generator creates
+ a new unique name, so we keep the first generated name cached and don't
+ generate more names. (Generator are simple utility classes)
+ '''
+ name = self.storage().readData('name')
+ if name is None:
+ name = self.nameGenerator().get( self.service().getBaseName()
+ + '-' + self.service().getColour(), 3 )
+ # Store value for persistence
+ self.storage().saveData('name', name)
+
+ return name
+
+ def setIp(self, ip):
+ '''
+ In our case, there is no OS manager associated with this, so this method
+ will never get called, but we put here as sample.
+
+ Whenever an os manager actor notifies the broker the state of the service
+ (mainly machines), the implementation of that os manager can (an probably will)
+ need to notify the IP of the deployed service. Remember that UDS treats with
+ IP services, so will probable needed in every service that you will create.
+ :note: This IP is the IP of the "consumed service", so the transport can
+ access it.
+ '''
+ self.storage().saveData('ip', str(ip))
+
+ def getUniqueId(self):
+ '''
+ Return and unique identifier for this service.
+ In our case, we will generate a mac name, that can be also as sample
+ of 'mac' generator use, and probably will get used something like this
+ at some services.
+
+ The get method of a mac generator takes one param, that is the mac range
+ to use to get an unused mac.
+ '''
+ mac = self.storage().readData('mac')
+ if mac is None:
+ mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
+ self.storage().saveData('mac', mac)
+ return mac
+
+ def getIp(self):
+ '''
+ We need to implement this method, so we can return the IP for transports
+ use. If no IP is known for this service, this must return None
+
+ If our sample do not returns an IP, IP transport will never work with
+ this service. Remember in real cases to return a valid IP address if
+ the service is accesible and you alredy know that (for example, because
+ the IP has been assigend via setIp by an os manager) or because
+ you get it for some other method.
+
+ Storage returns None if key is not stored.
+
+ :note: Keeping the IP address is responsibility of the User Deployment.
+ Every time the core needs to provide the service to the user, or
+ show the IP to the administrator, this method will get called
+
+ '''
+ ip = self.storage().readData('ip')
+ if ip is None:
+ ip = '192.168.0.34' # Sample IP for testing purposses only
+ return ip
+
+ def setReady(self):
+ '''
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+
+ This method exist for this kind of situations (i will explain it with a
+ sample)
+
+ Imagine a Service tree (Provider, Service, ...) for virtual machines.
+ This machines will get created by the UserDeployment implementation, but,
+ at some time, the machine can be put at in an state (suspend, shut down)
+ that will make the transport impossible to connect with it.
+
+ This method, in this case, will check the state of the machine, and if
+ it is "ready", that is, powered on and accesible, it will return
+ "State.FINISHED". If the machine is not accesible (has ben erased, for
+ example), it will return "State.ERROR" and store a reason of error so UDS
+ can ask for it and present this information to the Administrator.
+
+ If the machine powered off, or suspended, or any other state that is not
+ directly usable but can be put in an usable state, it will return
+ "State.RUNNING", and core will use checkState to see when the operation
+ has finished.
+
+ I hope this sample is enough to explain the use of this method..
+ '''
+
+ # In our case, the service is always ready
+ return State.FINISHED
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The user parameter is not realy neded, but provided. It indicates the
+ Database User Object (see py:mod:`uds.modules`) to which this deployed
+ user service will be assigned to.
+
+ This method will get called whenever a new deployed service for an user
+ is needed. This will give this class the oportunity to create
+ a service that is assigned to an user.
+
+ The way of using this method is as follows:
+
+ If the service gets created in "one step", that is, before the return
+ of this method, the consumable service for the user gets created, it
+ will return "State.FINISH".
+ If the service needs more steps (as in this case), we will return
+ "State.RUNNING", and if it has an error, it wil return "State.ERROR" and
+ store an error string so administration interface can show it.
+
+ We do not use user for anything, as in most cases will be.
+ '''
+ import random
+
+ self.storage().saveData('count', '0')
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self.storage().saveData('error', 'Random error at deployForUser :-)')
+ return State.ERROR
+
+ return State.RUNNING
+
+
+ def checkState(self):
+ '''
+ Our deployForUser method will initiate the consumable service deployment,
+ but will not finish it.
+
+ So in our sample, we will only check if a number reaches 5, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One deployForUser returns State.RUNNING, this task will get called until
+ checkState returns State.FINISHED.
+
+ Also, we will make the publication fail one of every 10 calls to this
+ method.
+
+ Note: Destroying, canceling and deploying for cache also makes use of
+ this method, so you must keep the info of that you are checking if you
+ need it.
+ In our case, destroy is 1-step action so this will no get called while
+ destroying, and cancel will simply invoke destroy
+ '''
+ import random
+
+ count = int(self.storage().readData('count')) + 1
+ # Count is always a valid value, because this method will never get
+ # called before deployForUser, deployForCache, destroy or cancel.
+ # In our sample, we only use checkState in case of deployForUser,
+ # so at first call count will be 0.
+ if count >= 5:
+ return State.FINISHED
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self.storage().saveData('error', 'Random error at checkState :-)')
+ return State.ERROR
+
+ self.storage().saveData('count', str(count))
+ return State.RUNNING
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter wether it is for cache or for an user)
+
+ This gives the oportunity to make something at that moment.
+ :note: You can also make these operations at checkState, this is really
+ not needed, but can be provided (default implementation of base class does
+ nothing)
+ '''
+ # Note that this is not really needed, is just a sample of storage use
+ self.storage().remove('count')
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This gives the User Deployment an oportunity to do whatever actions
+ are required so the service puts at a correct state for using by a service.
+
+ In our sample, the service is always ready, so this does nothing.
+
+ This is not a task method. All level 1 cache items can be diretly
+ assigned to an user with no more work needed, but, if something is needed,
+ here you can do whatever you need
+ '''
+ pass
+
+ def userLoggedIn(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We store the value at storage, but never get used, just an example
+ self.storage().saveData('user', user)
+
+ def userLoggedOut(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We do nothing more that remove the user
+ self.storage().remove('user')
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+ '''
+ return self.storage().readData('error') or 'No error'
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Invoked for destroying a deployed service
+ Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
+ @return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState)
+ '''
+ return State.FINISHED
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+ '''
+ return State.FINISHED
+
\ No newline at end of file
diff --git a/trunk/server/src/uds/services/Sample/SampleUserDeploymentTwo.py b/trunk/server/src/uds/services/Sample/SampleUserDeploymentTwo.py
new file mode 100644
index 00000000..687971e7
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/SampleUserDeploymentTwo.py
@@ -0,0 +1,469 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+from uds.core.services import UserDeployment
+from uds.core.util.State import State
+import logging
+
+logger = logging.getLogger(__name__)
+
+class SampleUserDeploymentTwo(UserDeployment):
+ '''
+ This class generates the user consumable elements of the service tree.
+
+ This is almost the same as SampleUserDeploymentOne, but differs that this one
+ uses the publication to get data from it, in a very basic way.
+
+ After creating at administration interface an Deployed Service, UDS will
+ create consumable services for users using UserDeployment class as
+ provider of this elements.
+
+ At class instantiation, this will receive an environment with"generator",
+ that are classes that provides a way to generate unique items.
+
+ The generators provided right now are 'mac' and 'name'. To get more info
+ about this, look at py:class:`uds.core.util.UniqueMacGenerator.UniqueNameGenerator`
+ and py:class:`uds.core.util.UniqueNameGenerator.UniqueNameGenerator`
+
+ As sample also of environment storage usage, wi will use here the provider
+ storage to keep all our needed info, leaving marshal and unmarshal (needed
+ by Serializable classes, like this) empty (that is, returns '' first and does
+ nothing the second one)
+
+ Also Remember, if you don't include this class as the deployedType of the
+ SampleServiceTwo, or whenever you try to access a service of SampleServiceTwo,
+ you will get an exception that says that you haven't included the deployedType.
+ '''
+
+ #: Recheck every five seconds by default (for task methods)
+ suggestedTime = 2
+
+ def initialize(self):
+ '''
+ Initialize default attributes values here. We can do whatever we like,
+ but for this sample this is just right...
+ '''
+ self._name = ''
+ self._ip = ''
+ self._mac = ''
+ self._error = ''
+ self._count = 0
+
+ # Serializable needed methods
+ def marshal(self):
+ '''
+ Marshal own data, in this sample we will marshal internal needed
+ attributes.
+
+ In this case, the data will be store with the database record. To
+ minimize database storage usage, we will "zip" data before returning it.
+ Anyway, we should keep this data as low as possible, we also have an
+ storage for loading larger data.
+
+ :note: It's a good idea when providing marshalers, to store a 'version'
+ beside the values, so we can, at a later stage, treat with old
+ data for current modules.
+ '''
+ data = '\t'.join(['v1', self._name, self._ip, self._mac, self._error,
+ str(self._count)])
+ return data.encode('zip')
+
+ def unmarshal(self, str_):
+ '''
+ We unmarshal the content.
+ '''
+ data = str_.decode('zip').split('\t')
+ # Data Version check
+ # If we include some new data at some point in a future, we can
+ # add "default" values at v1 check, and load new values at 'v2' check.
+ if data[0] == 'v1':
+ self._name, self._ip, self._mac, self._error, count = data[1:]
+ self._count = int(count)
+
+ def getName(self):
+ '''
+ We override this to return a name to display. Default implementation
+ (in base class), returns getUniqueIde() value
+ This name will help user to identify elements, and is only used
+ at administration interface.
+
+ We will use here the environment name provided generator to generate
+ a name for this element.
+
+ The namaGenerator need two params, the base name and a length for a
+ numeric incremental part for generating unique names. This are unique for
+ all UDS names generations, that is, UDS will not generate this name again
+ until this name is freed, or object is removed, what makes its environment
+ to also get removed, that makes all unique ids (names and macs right now)
+ to also get released.
+
+ Every time get method of a generator gets called, the generator creates
+ a new unique name, so we keep the first generated name cached and don't
+ generate more names. (Generator are simple utility classes)
+ '''
+ if self._name == '':
+ self._name = self.nameGenerator().get( self.publication().getBaseName(),
+ 3 )
+ # self._name will be stored when object is marshaled
+ return self._name
+
+ def setIp(self, ip):
+ '''
+ In our case, there is no OS manager associated with this, so this method
+ will never get called, but we put here as sample.
+
+ Whenever an os manager actor notifies the broker the state of the service
+ (mainly machines), the implementation of that os manager can (an probably will)
+ need to notify the IP of the deployed service. Remember that UDS treats with
+ IP services, so will probable needed in every service that you will create.
+ :note: This IP is the IP of the "consumed service", so the transport can
+ access it.
+ '''
+ self._ip = ip
+
+ def getUniqueId(self):
+ '''
+ Return and unique identifier for this service.
+ In our case, we will generate a mac name, that can be also as sample
+ of 'mac' generator use, and probably will get used something like this
+ at some services.
+
+ The get method of a mac generator takes one param, that is the mac range
+ to use to get an unused mac.
+
+ The mac generated is not used by anyone, it will not depend on
+ the range, the generator will take care that this mac is unique
+ and in the range provided, or it will return None. The ranges
+ are wide enough to ensure that we always will get a mac address
+ in this case, but if this is not your case, take into account that
+ None is a possible return value, and in that case, you should return an
+ invalid id right now. Every time a task method is invoked, the core
+ will try to update the value of the unique id using this method, so
+ that id can change with time. (In fact, it's not unique at database level,
+ it's unique in the sense that you must return an unique id that can, for
+ example, be used by os managers to identify this element).
+
+ :note: Normally, getting out of macs in the mac pool is a bad thing... :-)
+ '''
+ if self._mac == '':
+ self._mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
+ return self._mac
+
+ def getIp(self):
+ '''
+ We need to implement this method, so we can return the IP for transports
+ use. If no IP is known for this service, this must return None
+
+ If our sample do not returns an IP, IP transport will never work with
+ this service. Remember in real cases to return a valid IP address if
+ the service is accesible and you alredy know that (for example, because
+ the IP has been assigend via setIp by an os manager) or because
+ you get it for some other method.
+
+ Storage returns None if key is not stored.
+
+ :note: Keeping the IP address is responsibility of the User Deployment.
+ Every time the core needs to provide the service to the user, or
+ show the IP to the administrator, this method will get called
+
+ '''
+ if self._ip == '':
+ return '192.168.0.34' # Sample IP for testing purposes only
+ return self._ip
+
+ def setReady(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The method is invoked whenever a machine is provided to an user, right
+ before presenting it (via transport rendering) to the user.
+
+ This method exist for this kind of situations (i will explain it with a
+ sample)
+
+ Imagine a Service tree (Provider, Service, ...) for virtual machines.
+ This machines will get created by the UserDeployment implementation, but,
+ at some time, the machine can be put at in an state (suspend, shut down)
+ that will make the transport impossible to connect with it.
+
+ This method, in this case, will check the state of the machine, and if
+ it is "ready", that is, powered on and accessible, it will return
+ "State.FINISHED". If the machine is not accessible (has been erased, for
+ example), it will return "State.ERROR" and store a reason of error so UDS
+ can ask for it and present this information to the Administrator.
+
+ If the machine powered off, or suspended, or any other state that is not
+ directly usable but can be put in an usable state, it will return
+ "State.RUNNING", and core will use checkState to see when the operation
+ has finished.
+
+ I hope this sample is enough to explain the use of this method..
+ '''
+
+ # In our case, the service is always ready
+ return State.FINISHED
+
+ def deployForUser(self, user):
+ '''
+ Deploys an service instance for an user.
+
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ The user parameter is not realy neded, but provided. It indicates the
+ Database User Object (see py:mod:`uds.modules`) to which this deployed
+ user service will be assigned to.
+
+ This method will get called whenever a new deployed service for an user
+ is needed. This will give this class the oportunity to create
+ a service that is assigned to an user.
+
+ The way of using this method is as follows:
+
+ If the service gets created in "one step", that is, before the return
+ of this method, the consumable service for the user gets created, it
+ will return "State.FINISH".
+ If the service needs more steps (as in this case), we will return
+ "State.RUNNING", and if it has an error, it wil return "State.ERROR" and
+ store an error string so administration interface can show it.
+
+ We do not use user for anything, as in most cases will be.
+ '''
+ import random
+
+ self._count = 0
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ # Note that we can mark this string as translatable, and return
+ # it translated at reasonOfError method
+ self._error = 'Random error at deployForUser :-)'
+ return State.ERROR
+
+ return State.RUNNING
+
+ def deployForCache(self, cacheLevel):
+ '''
+ Deploys a user deployment as cache.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ In our sample, this will do exactly the same as deploy for user,
+ except that it will never will give an error.
+
+ See deployForUser for a description of what this method should do.
+
+ :note: deployForCache is invoked whenever a new cache element is needed
+ for an specific user deployment. It will also indicate for what
+ cache level (L1, L2) is the deployment
+ '''
+ self._count = 0
+ return State.RUNNING
+
+ def moveToCache(self, newLevel):
+ '''
+ This method is invoked whenever the core needs to move from the current
+ cache level to a new cache level an user deployment.
+
+ This is a task method. As that, the expected return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ We only provide newLevel, because there is only two cache levels, so if
+ newLevel is L1, the actual is L2, and if it is L2, the actual is L1.
+
+ Actually there is no possibility to move assigned services again back to
+ cache. If some service needs that kind of functionallity, this must be
+ provided at service level (for example, when doing publishing creating
+ a number of services that will be used, released and reused by users).
+
+ Also, user deployments that are at cache level 2 will never get directly
+ assigned to user. First, it will pass to L1 and then it will get assigned.
+
+ A good sample of a real implementation of this is moving a virtual machine
+ from a "suspended" state to "running" state to assign it to an user.
+
+ In this sample, there is L2 cache also, but moving from L1 to L2 and
+ from L2 to L1 is doing really nothing, so this method will do nothing.
+
+ In a real scenario, we will, for example, suspend or resume virtual machine
+ and, return State.RUNNING and at checkState check if this task is completed.
+ '''
+ pass
+
+ def checkState(self):
+ '''
+ Our deployForUser method will initiate the consumable service deployment,
+ but will not finish it.
+
+ So in our sample, we will only check if a number reaches 5, and if so
+ return that we have finished, else we will return that we are working
+ on it.
+
+ One deployForUser returns State.RUNNING, this task will get called until
+ checkState returns State.FINISHED.
+
+ Also, we will make the user deployment fail one of every 10 calls to this
+ method.
+
+ Note: Destroying, canceling and deploying for cache also makes use of
+ this method, so you must keep the info of that you are checking if you
+ need it.
+
+ In our case, destroy is 1-step action so this will no get called while
+ destroying, and cancel will simply invoke destroy. Cache deployment is
+ exactly as user deployment, except that the core will not assign it to
+ anyone, and cache moving operations is
+ '''
+ import random
+
+ self._count += 1
+ # Count is always a valid value, because this method will never get
+ # called before deployForUser, deployForCache, destroy or cancel.
+ # In our sample, we only use checkState in case of deployForUser,
+ # so at first call count will be 0.
+ if self._count >= 5:
+ return State.FINISHED
+
+ # random fail
+ if random.randint(0, 9) == 9:
+ self._error = 'Random error at checkState :-)'
+ return State.ERROR
+
+ return State.RUNNING
+
+ def finish(self):
+ '''
+ Invoked when the core notices that the deployment of a service has finished.
+ (No matter whether it is for cache or for an user)
+
+ This gives the opportunity to make something at that moment.
+
+ :note: You can also make these operations at checkState, this is really
+ not needed, but can be provided (default implementation of base class does
+ nothing)
+ '''
+ # We set count to 0, not needed but for sample purposes
+ self._count = 0
+
+ def assignToUser(self, user):
+ '''
+ This method is invoked whenever a cache item gets assigned to an user.
+ This is not a task method right now, simply a notification. This means
+ that L1 cache items must be directly usable (except for the readyness part)
+ by users in a single step operation.
+
+ Note that there will be an setReady call before letting the user consume
+ this user deployment, so this is more informational (so, if you keep at
+ what cache level is this instance, you can update it) than anything else.
+
+ This is not a task method. All level 1 cache items can be dircetly
+ assigned to an user with no more work needed, but, if something is needed,
+ here you can do whatever you need.
+
+ user is a Database user object.
+ '''
+ logger.debug('Assigned to user {0}'.format(user))
+
+ def userLoggedIn(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged into a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responsibility of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actors.
+ '''
+ # We store the value at storage, but never get used, just an example
+ self.storage().saveData('user', user)
+
+ def userLoggedOut(self, user):
+ '''
+ This method must be available so os managers can invoke it whenever
+ an user get logged out if a service.
+
+ Default implementation does nothing, so if you are going to do nothing,
+ you don't need to implement it.
+
+ The responability of notifying it is of os manager actor, and it's
+ directly invoked by os managers (right now, linux os manager and windows
+ os manager)
+
+ The user provided is just an string, that is provided by actor.
+ '''
+ # We do nothing more that remove the user
+ self.storage().remove('user')
+
+ def reasonOfError(self):
+ '''
+ Returns the reason of the error.
+
+ Remember that the class is responsible of returning this whenever asked
+ for it, and it will be asked everytime it's needed to be shown to the
+ user (when the administation asks for it).
+
+ :note: Remember that you can use ugettext to translate this error to
+ user language whenever it is possible. (This one will get invoked
+ directly from admin interface and, as so, will have translation
+ environment correctly set up.
+ '''
+ return self._error
+
+ def destroy(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ Invoked for destroying a deployed service
+ Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
+ @return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState)
+ '''
+ return State.FINISHED
+
+ def cancel(self):
+ '''
+ This is a task method. As that, the excepted return values are
+ State values RUNNING, FINISHED or ERROR.
+
+ This can be invoked directly by an administration or by the clean up
+ of the deployed service (indirectly).
+ When administrator requests it, the cancel is "delayed" and not
+ invoked directly.
+ '''
+ return State.FINISHED
diff --git a/trunk/server/src/uds/services/Sample/__init__.py b/trunk/server/src/uds/services/Sample/__init__.py
new file mode 100644
index 00000000..38a9126a
--- /dev/null
+++ b/trunk/server/src/uds/services/Sample/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Sample Service module.
+
+This package simply shows how a new service can be implemented.
+
+
+The first thing to do in every package that is a module is register the
+class that is responsible of providing the module with the system.
+
+For this, we must simply import the class at __init__, UDS will take care
+of the rest
+'''
+
+from SampleProvider import Provider
+
diff --git a/trunk/server/src/uds/services/Sample/provider.png b/trunk/server/src/uds/services/Sample/provider.png
new file mode 100644
index 00000000..d2a954d4
Binary files /dev/null and b/trunk/server/src/uds/services/Sample/provider.png differ
diff --git a/trunk/server/src/uds/services/Sample/service.png b/trunk/server/src/uds/services/Sample/service.png
new file mode 100644
index 00000000..c7b626c4
Binary files /dev/null and b/trunk/server/src/uds/services/Sample/service.png differ
diff --git a/trunk/server/src/uds/services/__init__.py b/trunk/server/src/uds/services/__init__.py
new file mode 100644
index 00000000..48b9fbba
--- /dev/null
+++ b/trunk/server/src/uds/services/__init__.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2012 Virtual Cable S.L.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of Virtual Cable S.L. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+'''
+Service modules for uds are contained inside this package.
+To create a new service module, you will need to follow this steps:
+ 1.- Create the service module, probably based on an existing one
+ 2.- Insert the module package as child of this package
+ 3.- Import the class of your service module at __init__. For example::
+ from Service import SimpleService
+ 4.- Done. At Server restart, the module will be recognized, loaded and treated
+
+The registration of modules is done locating subclases of :py:class:`uds.core.auths.Authentication`
+
+.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
+'''
+
+def __init__():
+ '''
+ This imports all packages that are descendant of this package, and, after that,
+ it register all subclases of service provider as
+ '''
+ import os.path, pkgutil
+ import sys
+ from uds.core import services
+
+ # Dinamycally import children of this package. The __init__.py files must register, if needed, inside ServiceProviderFactory
+ pkgpath = os.path.dirname(sys.modules[__name__].__file__)
+ for _, name, _ in pkgutil.iter_modules([pkgpath]):
+ __import__(name, globals(), locals(), [], -1)
+
+ p = services.ServiceProvider
+ # This is marked as error in IDE, but it's not (__subclasses__)
+ for cls in p.__subclasses__():
+ services.factory().insert(cls)
+
+__init__()
diff --git a/trunk/server/src/uds/static/css/reset.css b/trunk/server/src/uds/static/css/reset.css
new file mode 100644
index 00000000..e2f0075b
--- /dev/null
+++ b/trunk/server/src/uds/static/css/reset.css
@@ -0,0 +1,46 @@
+@CHARSET "UTF-8";
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
\ No newline at end of file
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 00000000..5b5dab2a
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 00000000..ac8b229a
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 00000000..ad3d6346
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 00000000..42ccba26
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 00000000..1d43b47e
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 00000000..86c2baa6
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 00000000..4443fdc1
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 00000000..7c9fa6c6
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-icons_222222_256x240.png b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_222222_256x240.png
new file mode 100644
index 00000000..b273ff11
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_222222_256x240.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-icons_2e83ff_256x240.png b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 00000000..09d1cdc8
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_2e83ff_256x240.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-icons_454545_256x240.png b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_454545_256x240.png
new file mode 100644
index 00000000..59bd45b9
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_454545_256x240.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-icons_888888_256x240.png b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_888888_256x240.png
new file mode 100644
index 00000000..6d02426c
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_888888_256x240.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/images/ui-icons_cd0a0a_256x240.png b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 00000000..2ab019b7
Binary files /dev/null and b/trunk/server/src/uds/static/css/smoothness/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/trunk/server/src/uds/static/css/smoothness/jquery-ui-1.8.17.custom.css b/trunk/server/src/uds/static/css/smoothness/jquery-ui-1.8.17.custom.css
new file mode 100644
index 00000000..537f4303
--- /dev/null
+++ b/trunk/server/src/uds/static/css/smoothness/jquery-ui-1.8.17.custom.css
@@ -0,0 +1,307 @@
+/*
+ * jQuery UI CSS Framework 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*
+ * jQuery UI Dialog 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
diff --git a/trunk/server/src/uds/static/css/uds.css b/trunk/server/src/uds/static/css/uds.css
new file mode 100644
index 00000000..bdf458b0
--- /dev/null
+++ b/trunk/server/src/uds/static/css/uds.css
@@ -0,0 +1,237 @@
+@CHARSET "UTF-8";
+
+body,a{
+ font-family:monospace;
+ font-size:1em;
+ color:#666666;
+
+
+}
+body{background: url("../img/bg_uds.png") no-repeat center 6em ;}
+
+legend{
+ padding:0 2em;
+}
+ul{
+ width:60%;
+ text-align:left;
+ margin:auto;
+}
+
+ul li ul{
+ width:100%;
+}
+ul.archivos li{
+ background: url("../img/menos.png") no-repeat scroll 0.5em 0.5em #EDF3E3;
+ line-height: 2em;
+ margin: 1em 0;
+ padding-left: 2.5em;
+}
+ul.archivos li a{
+ font-weight:900;
+ display:block;
+}
+ul.archivos li dfn{
+ font-style:italic;
+
+}
+#backtolist{
+ width:100%;
+ clear:both;
+ margin:4em 0;
+}
+#header{
+ background:url('../img/bg_barra_superior2.png') repeat-x #000;
+ color:#ffffff;
+ padding:0.8em;
+ text-align:right;
+ border-bottom:groove 6px #2b5694;
+ padding-bottom:1em;
+ }
+
+
+
+#header a{color:#ffffff}
+
+#header div{
+ display:inline-block;
+ padding:0em 0.6em;
+ border-right:solid 2px #ffffff;
+}
+
+#header img{
+ margin:0 0 0 0.4em;
+ vertical-align:text-top;}
+
+#header #lang img{
+ border: outset 1px #666666;
+
+}
+
+
+#header #lheader{float:left;border:0;}
+#lheader a{text-decoration:none;}
+#lheader h1{
+ font-size:2em;
+ margin-top:-0.1em;
+
+}
+#lheader a span{color:#2b5694}
+
+#header .last{border-right:0}
+#header #admin{cursor:pointer;text-decoration:underline;}
+#header #admin ul{
+ position:absolute;background:#2b5694;padding:1em;width:20em;
+ opacity:0.8;
+ filter:alpha(opacity=80);
+ margin-top:0.51em;
+ list-style:decimal-leading-zero inside url('../img/menos.png');
+
+
+}
+#header #admin ul li a{
+ padding-bottom:0.6em;
+ vertical-align: middle;
+}
+
+#content{
+ text-align:center;
+
+ }
+
+/*desde*/
+#content form{
+ width:40em;
+ margin:auto;
+ margin-top:4em;
+}
+#content form fieldset{
+ border:solid 1px #2b5694;
+ background:#d5e2ff;
+ padding:0 1.2em;
+ margin-top:2em;
+ position:relative;
+ }
+#services ul li{
+ padding-bottom:1em;
+ border:solid 1px #666666;
+ background-color:#f1f1f1;
+ padding:0;
+ margin:1px;
+ font-size:1.2em;
+
+}
+#services ul li a{
+ padding-left:1em;
+
+}
+#services ul li span{
+ display:inline-block;
+ width:1em;
+ height:1em;
+ margin:0.5em 0.5em 0 0;
+ float:right;
+ color:#F1F1F1;
+ background:url('../img/down.png') top right no-repeat;
+}
+
+#services ul li span.ampli{
+ background:url('../img/up.png') top right no-repeat;
+}
+#services ul li ul{
+ border-top:ridge #2b5694 3px;
+}
+#services ul li ul li{
+ border:0;
+ background-color:#f9f9f9;
+
+ font-size:0.81em;
+}
+#services ul li ul li img{
+ vertical-align:middle;
+ padding-right:0.2em;
+}
+#services li{
+ padding-left:1em;
+ line-height:1.8em;
+
+}
+
+h2{
+ background: url("../img/bg_barra_pie.png") repeat-x scroll center top #FFFFFF;
+ /*border: 4px groove #2b5694;*/
+ border:solid 1px #2b5694;
+ color: #2b5694;
+ font-size: 1.2em;
+ font-weight: 900;
+ margin: 2em;
+ padding: 0.8em;
+ text-align: center;
+}
+
+#login fieldset{
+ margin:1.2em;
+ font-size:1em;
+
+
+}
+#login fieldset p,#content form fieldset p{
+ width:100%;
+ display:inline-block;
+ float:left;
+ margin:0.51em 0;
+
+}
+#login fieldset label,#content form fieldset label{
+ line-height:2em;
+ display:block;
+ font-size:1em;
+ float:left;
+}
+input,buttom,select{font-family:monospace;font-size:1em;float:right;width:17em;}
+select{
+ width:17.4em;
+}
+
+input[type=submit]{
+ background: url("../img/bg_barra_superior2.png") repeat-x scroll 40px -10px #000000;
+ color: #FFFFFF;
+ padding: 0.2em 1.2em;
+ width: auto;
+}
+
+#customHtml {
+ margin-top: 4em;
+};
+
+#adminmsgs{
+ text-align:left;
+ margin:auto;
+ margin-top:2em;
+ padding:2em;
+ background:#f2f2f2;
+ width:40em;
+ border: 1px solid #666666;
+}
+
+#footer{
+ background: url("../img/bg_barra_pie.png") repeat-x scroll center top #FFFFFF;
+ padding:1.6% 2%;
+ margin-top:4em;
+ text-align:right;
+ min-height:1em;
+ border-top:groove 2px #2b5694;
+ position:absolute;
+ width:96%;
+ bottom:0;
+
+}
+
+#applet {
+ margin:20px 0;
+}
+
+/* Sample "info" (footer) text, to be placed after (c) Virtualcable */
+/*#vcable:after {
+ content: "Testing text";
+}*/
diff --git a/trunk/server/src/uds/static/img/2downarrow.png b/trunk/server/src/uds/static/img/2downarrow.png
new file mode 100644
index 00000000..9f89b239
Binary files /dev/null and b/trunk/server/src/uds/static/img/2downarrow.png differ
diff --git a/trunk/server/src/uds/static/img/2uparrow.png b/trunk/server/src/uds/static/img/2uparrow.png
new file mode 100644
index 00000000..7bc9ab52
Binary files /dev/null and b/trunk/server/src/uds/static/img/2uparrow.png differ
diff --git a/trunk/server/src/uds/static/img/access.png b/trunk/server/src/uds/static/img/access.png
new file mode 100644
index 00000000..cd64a95b
Binary files /dev/null and b/trunk/server/src/uds/static/img/access.png differ
diff --git a/trunk/server/src/uds/static/img/bg_barra_pie.png b/trunk/server/src/uds/static/img/bg_barra_pie.png
new file mode 100644
index 00000000..70b3697d
Binary files /dev/null and b/trunk/server/src/uds/static/img/bg_barra_pie.png differ
diff --git a/trunk/server/src/uds/static/img/bg_barra_superior2.png b/trunk/server/src/uds/static/img/bg_barra_superior2.png
new file mode 100644
index 00000000..288cc78c
Binary files /dev/null and b/trunk/server/src/uds/static/img/bg_barra_superior2.png differ
diff --git a/trunk/server/src/uds/static/img/bg_uds.png b/trunk/server/src/uds/static/img/bg_uds.png
new file mode 100644
index 00000000..b29cb791
Binary files /dev/null and b/trunk/server/src/uds/static/img/bg_uds.png differ
diff --git a/trunk/server/src/uds/static/img/down.png b/trunk/server/src/uds/static/img/down.png
new file mode 100644
index 00000000..72708b99
Binary files /dev/null and b/trunk/server/src/uds/static/img/down.png differ
diff --git a/trunk/server/src/uds/static/img/exit.png b/trunk/server/src/uds/static/img/exit.png
new file mode 100644
index 00000000..4596d267
Binary files /dev/null and b/trunk/server/src/uds/static/img/exit.png differ
diff --git a/trunk/server/src/uds/static/img/favicon.ico b/trunk/server/src/uds/static/img/favicon.ico
new file mode 100644
index 00000000..7da31415
Binary files /dev/null and b/trunk/server/src/uds/static/img/favicon.ico differ
diff --git a/trunk/server/src/uds/static/img/favicon.png b/trunk/server/src/uds/static/img/favicon.png
new file mode 100644
index 00000000..8f5be517
Binary files /dev/null and b/trunk/server/src/uds/static/img/favicon.png differ
diff --git a/trunk/server/src/uds/static/img/flags/de.png b/trunk/server/src/uds/static/img/flags/de.png
new file mode 100644
index 00000000..8c21120d
Binary files /dev/null and b/trunk/server/src/uds/static/img/flags/de.png differ
diff --git a/trunk/server/src/uds/static/img/flags/en.png b/trunk/server/src/uds/static/img/flags/en.png
new file mode 100644
index 00000000..0bfeaf91
Binary files /dev/null and b/trunk/server/src/uds/static/img/flags/en.png differ
diff --git a/trunk/server/src/uds/static/img/flags/es.png b/trunk/server/src/uds/static/img/flags/es.png
new file mode 100644
index 00000000..5ddb8e85
Binary files /dev/null and b/trunk/server/src/uds/static/img/flags/es.png differ
diff --git a/trunk/server/src/uds/static/img/flags/fr.png b/trunk/server/src/uds/static/img/flags/fr.png
new file mode 100644
index 00000000..d57e05bc
Binary files /dev/null and b/trunk/server/src/uds/static/img/flags/fr.png differ
diff --git a/trunk/server/src/uds/static/img/ico-mas.png b/trunk/server/src/uds/static/img/ico-mas.png
new file mode 100644
index 00000000..b4b3ed10
Binary files /dev/null and b/trunk/server/src/uds/static/img/ico-mas.png differ
diff --git a/trunk/server/src/uds/static/img/ico-menos.png b/trunk/server/src/uds/static/img/ico-menos.png
new file mode 100644
index 00000000..fd37a925
Binary files /dev/null and b/trunk/server/src/uds/static/img/ico-menos.png differ
diff --git a/trunk/server/src/uds/static/img/mas.png b/trunk/server/src/uds/static/img/mas.png
new file mode 100644
index 00000000..64fb7f93
Binary files /dev/null and b/trunk/server/src/uds/static/img/mas.png differ
diff --git a/trunk/server/src/uds/static/img/menos.png b/trunk/server/src/uds/static/img/menos.png
new file mode 100644
index 00000000..c50a5255
Binary files /dev/null and b/trunk/server/src/uds/static/img/menos.png differ
diff --git a/trunk/server/src/uds/static/img/unknown.png b/trunk/server/src/uds/static/img/unknown.png
new file mode 100644
index 00000000..c0c02862
Binary files /dev/null and b/trunk/server/src/uds/static/img/unknown.png differ
diff --git a/trunk/server/src/uds/static/img/up.png b/trunk/server/src/uds/static/img/up.png
new file mode 100644
index 00000000..6fe3b489
Binary files /dev/null and b/trunk/server/src/uds/static/img/up.png differ
diff --git a/trunk/server/src/uds/static/img/volver.png b/trunk/server/src/uds/static/img/volver.png
new file mode 100644
index 00000000..f78956fa
Binary files /dev/null and b/trunk/server/src/uds/static/img/volver.png differ
diff --git a/trunk/server/src/uds/static/js/PluginDetect_Java.js b/trunk/server/src/uds/static/js/PluginDetect_Java.js
new file mode 100644
index 00000000..562fe480
--- /dev/null
+++ b/trunk/server/src/uds/static/js/PluginDetect_Java.js
@@ -0,0 +1,7 @@
+/*
+PluginDetect v0.7.8 www.pinlady.net/PluginDetect/
+www.pinlady.net/PluginDetect/license/
+[ getVersion isMinVersion onDetectionDone onWindowLoaded ]
+[ Java(OTF & NOTF) ]
+*/
+var PluginDetect={version:"0.7.8",name:"PluginDetect",handler:function(c,b,a){return function(){c(b,a)}},isDefined:function(b){return typeof b!="undefined"},isArray:function(b){return(/array/i).test(Object.prototype.toString.call(b))},isFunc:function(b){return typeof b=="function"},isString:function(b){return typeof b=="string"},isNum:function(b){return typeof b=="number"},isStrNum:function(b){return(typeof b=="string"&&(/\d/).test(b))},getNumRegx:/[\d][\d\.\_,-]*/,splitNumRegx:/[\.\_,-]/g,getNum:function(b,c){var d=this,a=d.isStrNum(b)?(d.isDefined(c)?new RegExp(c):d.getNumRegx).exec(b):null;return a?a[0]:null},compareNums:function(h,f,d){var e=this,c,b,a,g=parseInt;if(e.isStrNum(h)&&e.isStrNum(f)){if(e.isDefined(d)&&d.compareNums){return d.compareNums(h,f)}c=h.split(e.splitNumRegx);b=f.split(e.splitNumRegx);for(a=0;ag(b[a],10)){return 1}if(g(c[a],10)c||!(/\d/).test(e[a])){e[a]="0"}}return e.slice(0,4).join(",")},$$hasMimeType:function(a){return function(c){if(!a.isIE&&c){var f,e,b,d=a.isArray(c)?c:(a.isString(c)?[c]:[]);for(b=0;b0&&!f[g]){f[g]=f[a](f);delete f[a]}}catch(d){}}}},initObj:function(e,b,d){var a,c;if(e){if(e[b[0]]==1||d){for(a=0;a=0;f=f-2){if(d[f]&&new RegExp(d[f],"i").test(b)){c.OS=d[f+1];break}}}c.convertFuncs(c);c.head=(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]||document.body||null);c.isIE=(new Function("return "+e+"*@cc_on!@*"+e+"false"))();c.verIE=c.isIE&&(/MSIE\s*(\d+\.?\d*)/i).test(i)?parseFloat(RegExp.$1,10):null;c.ActiveXEnabled=false;if(c.isIE){var f,j=["Msxml2.XMLHTTP","Msxml2.DOMDocument","Microsoft.XMLDOM","ShockwaveFlash.ShockwaveFlash","TDCCtl.TDCCtl","Shell.UIHelper","Scripting.Dictionary","wmplayer.ocx"];for(f=0;f0&&c.isFunc(b[0])))){a.push(b)}},callArray:function(b){var c=this,a;if(c.isArray(b)){for(a=0;a0&&b.isFunc(c[0])){c[0](b,a>1?c[1]:0,a>2?c[2]:0,a>3?c[3]:0)}else{if(b.isFunc(c)){c(b)}}},$$isMinVersion:function(a){return function(h,g,d,c){var e=a.init(h),f,b=-1,j={};if(e.status<0){return e.status}f=e.plugin;g=a.formatNum(a.isNum(g)?g.toString():(a.isStrNum(g)?a.getNum(g):"0"));if(f.getVersionDone!=1){f.getVersion(g,d,c);if(f.getVersionDone===null){f.getVersionDone=1}}a.cleanup();if(f.installed!==null){b=f.installed<=0.5?f.installed:(f.installed==0.7?1:(f.version===null?0:(a.compareNums(f.version,g,f)>=0?1:-0.1)))};return b}},getVersionDelimiter:",",$$getVersion:function(a){return function(g,d,c){var e=a.init(g),f,b,h={};if(e.status<0){return null};f=e.plugin;if(f.getVersionDone!=1){f.getVersion(null,d,c);if(f.getVersionDone===null){f.getVersionDone=1}}a.cleanup();b=(f.version||f.version0);b=b?b.replace(a.splitNumRegx,a.getVersionDelimiter):b;return b}},cleanup:function(){var a=this;if(a.garbage&&a.isDefined(window.CollectGarbage)){window.CollectGarbage()}},addWinEvent:function(d,c){var e=this,a=window,b;if(e.isFunc(c)){if(a.addEventListener){a.addEventListener(d,c,false)}else{if(a.attachEvent){a.attachEvent("on"+d,c)}else{b=a["on"+d];a["on"+d]=e.winHandler(c,b)}}}},winHandler:function(d,c){return function(){d();if(typeof c=="function"){c()}}},WLfuncs0:[],WLfuncs:[],runWLfuncs:function(a){var b={};a.winLoaded=true;a.callArray(a.WLfuncs0);a.callArray(a.WLfuncs);if(a.onDoneEmptyDiv){a.onDoneEmptyDiv()}},winLoaded:false,$$onWindowLoaded:function(a){return function(b){if(a.winLoaded){a.call(b)}else{a.fPush(b,a.WLfuncs)}}},$$onDetectionDone:function(a){return function(h,g,c,b){var d=a.init(h),k,e,j={};if(d.status==-3){return -1}e=d.plugin;if(!a.isArray(e.funcs)){e.funcs=[]}if(e.getVersionDone!=1){k=a.isMinVersion?a.isMinVersion(h,"0",c,b):a.getVersion(h,c,b)}if(e.installed!=-0.5&&e.installed!=0.5){a.call(g);return 1}if(e.NOTF){a.fPush(g,e.funcs);return 0}return 1}},div:null,divID:"plugindetect",divWidth:50,pluginSize:1,emptyDiv:function(){var d=this,b,h,c,a,f,g;if(d.div&&d.div.childNodes){for(b=d.div.childNodes.length-1;b>=0;b--){c=d.div.childNodes[b];if(c&&c.childNodes){for(h=c.childNodes.length-1;h>=0;h--){g=c.childNodes[h];try{c.removeChild(g)}catch(f){}}}if(c){try{d.div.removeChild(c)}catch(f){}}}}if(!d.div){a=document.getElementById(d.divID);if(a){d.div=a}}if(d.div&&d.div.parentNode){try{d.div.parentNode.removeChild(d.div)}catch(f){}d.div=null}},DONEfuncs:[],onDoneEmptyDiv:function(){var c=this,a,b;if(!c.winLoaded){return}if(c.WLfuncs&&c.WLfuncs.length&&c.WLfuncs[c.WLfuncs.length-1]!==null){return}for(a in c){b=c[a];if(b&&b.funcs){if(b.OTF==3){return}if(b.funcs.length&&b.funcs[b.funcs.length-1]!==null){return}}}for(a=0;a=i){return -1}try{if(l==c.pluginSize&&(!c.isIE||c.getDOMobj(m).readyState==4)){if(!m.winLoaded&&c.winLoaded){return 1}if(m.winLoaded&&c.isNum(b)){if(!c.isNum(m.count)){m.count=b}if(b-m.count>=10){return 1}}}}catch(f){}return 0},getDOMobj:function(g,a){var f,d=this,c=g?g.span:0,b=c&&c.firstChild?1:0;try{if(b&&a){d.div.focus()}}catch(f){}return b?c.firstChild:null},setStyle:function(b,g){var f=b.style,a,d,c=this;if(f&&g){for(a=0;ao'+c+"/div>");d=j.getElementById(b)}catch(h){}}g=(j.getElementsByTagName("body")[0]||j.body);if(g){if(g.firstChild&&f.isDefined(g.insertBefore)){g.insertBefore(a,g.firstChild)}else{g.appendChild(a)}if(d){g.removeChild(d)}}else{}},insertHTML:function(g,b,h,a,l){var m,n=document,k=this,q,p=n.createElement("span"),o,j,f="<";var c=["outlineStyle","none","borderStyle","none","padding","0px","margin","0px","visibility","visible"];var i="outline-style:none;border-style:none;padding:0px;margin:0px;visibility:visible;";if(!k.isDefined(a)){a=""}if(k.isString(g)&&(/[^\s]/).test(g)){g=g.toLowerCase().replace(/\s/g,"");q=f+g+' width="'+k.pluginSize+'" height="'+k.pluginSize+'" ';q+='style="'+i+'display:inline;" ';for(o=0;o";for(o=0;o'}}q+=a+f+"/"+g+">"}else{q=a}if(!k.div){j=n.getElementById(k.divID);if(j){k.div=j}else{k.div=n.createElement("div");k.div.id=k.divID}k.setStyle(k.div,c.concat(["width",k.divWidth+"px","height",(k.pluginSize+3)+"px","fontSize",(k.pluginSize+3)+"px","lineHeight",(k.pluginSize+3)+"px","verticalAlign","baseline","display","block"]));if(!j){k.setStyle(k.div,["position","absolute","right","0px","top","0px"]);k.insertDivInBody(k.div)}}if(k.div&&k.div.parentNode){k.setStyle(p,c.concat(["fontSize",(k.pluginSize+3)+"px","lineHeight",(k.pluginSize+3)+"px","verticalAlign","baseline","display","inline"]));try{p.innerHTML=q}catch(m){};try{k.div.appendChild(p)}catch(m){};return{span:p,winLoaded:k.winLoaded,tagName:g,outerHTML:q}}return{span:null,winLoaded:k.winLoaded,tagName:"",outerHTML:q}},file:{$:1,any:"fileStorageAny999",valid:"fileStorageValid999",save:function(d,f,c){var b=this,e=b.$,a;if(d&&e.isDefined(c)){if(!d[b.any]){d[b.any]=[]}if(!d[b.valid]){d[b.valid]=[]}d[b.any].push(c);a=b.split(f,c);if(a){d[b.valid].push(a)}}},getValidLength:function(a){return a&&a[this.valid]?a[this.valid].length:0},getAnyLength:function(a){return a&&a[this.any]?a[this.any].length:0},getValid:function(c,a){var b=this;return c&&c[b.valid]?b.get(c[b.valid],a):null},getAny:function(c,a){var b=this;return c&&c[b.any]?b.get(c[b.any],a):null},get:function(d,a){var c=d.length-1,b=this.$.isNum(a)?a:c;return(b<0||b>c)?null:d[b]},split:function(g,c){var b=this,e=b.$,f=null,a,d;g=g?g.replace(".","\\."):"";d=new RegExp("^(.*[^\\/])("+g+"\\s*)$");if(e.isString(c)&&d.test(c)){a=(RegExp.$1).split("/");f={name:a[a.length-1],ext:RegExp.$2,full:c};a[a.length-1]="";f.path=a.join("/")}return f},z:0},Plugins:{java:{mimeType:["application/x-java-applet","application/x-java-vm","application/x-java-bean"],classID:"clsid:8AD9C840-044E-11D1-B3E9-00805F499D93",navigator:{a:window.navigator.javaEnabled(),javaEnabled:function(){return this.a},mimeObj:0,pluginObj:0},OTF:null,minIEver:7,debug:0,debugEnable:function(){var a=this,b=a.$;a.debug=1},isDisabled:{$:1,DTK:function(){var a=this,c=a.$,b=a.$$;if((c.isGecko&&c.compareNums(c.verGecko,c.formatNum("1.6"))<=0)||(c.isSafari&&c.OS==1&&(!c.verSafari||c.compareNums(c.verSafari,"5,1,0,0")<0))||c.isChrome||(c.isIE&&!c.ActiveXEnabled)){return 1}return 0},AXO:function(){var a=this,c=a.$,b=a.$$;return(!c.isIE||!c.ActiveXEnabled||(!b.debug&&b.DTK.query().status!==0))},navMime:function(){var b=this,d=b.$,c=b.$$,a=c.navigator;if(d.isIE||!a.mimeObj||!a.pluginObj){return 1}return 0},navPlugin:function(){var b=this,d=b.$,c=b.$$,a=c.navigator;if(d.isIE||!a.mimeObj||!a.pluginObj){return 1}return 0},windowDotJava:function(){var a=this,c=a.$,b=a.$$;if(!window.java){return 1}if(c.OS==2&&c.verOpera&&c.verOpera<9.2&&c.verOpera>=9){return 1}if(c.verGecko&&c.compareNums(c.verGecko,"1,9,0,0")<0&&c.compareNums(c.verGecko,"1,8,0,0")>=0){return 1}return 0},allApplets:function(){var b=this,d=b.$,c=b.$$,a=c.navigator;if(d.OS>=20){return 0}if(d.verOpera&&d.verOpera<11&&!a.javaEnabled()&&!c.lang.System.getProperty()[0]){return 1}if((d.verGecko&&d.compareNums(d.verGecko,d.formatNum("2"))<0)&&!a.mimeObj&&!c.lang.System.getProperty()[0]){return 1}return 0},AppletTag:function(){var b=this,d=b.$,c=b.$$,a=c.navigator;return d.isIE?!a.javaEnabled():0},ObjectTag:function(){var a=this,c=a.$,b=a.$$;return c.isIE?!c.ActiveXEnabled:0},z:0},getVerifyTagsDefault:function(){var a=this,c=a.$,b=[1,0,1];if(c.OS>=20){return b}if((c.isIE&&(c.verIE<9||!c.ActiveXEnabled))||(c.verGecko&&c.compareNums(c.verGecko,c.formatNum("2"))<0)||(c.isSafari&&(!c.verSafari||c.compareNums(c.verSafari,c.formatNum("4"))<0))||(c.verOpera&&c.verOpera<10)){b=[1,1,1]}return b},getVersion:function(j,g,i){var b=this,d=b.$,e,a=b.applet,h=b.verify,k=b.navigator,f=null,l=null,c=null;if(b.getVersionDone===null){b.OTF=0;k.mimeObj=d.hasMimeType(b.mimeType);if(k.mimeObj){k.pluginObj=k.mimeObj.enabledPlugin}if(h){h.begin()}}a.setVerifyTagsArray(i);d.file.save(b,".jar",g);if(b.getVersionDone===0){if(a.should_Insert_Query_Any()){e=a.insert_Query_Any();b.setPluginStatus(e[0],e[1],f)}return}if((!f||b.debug)&&b.DTK.query().version){f=b.DTK.version}if((!f||b.debug)&&b.navMime.query().version){f=b.navMime.version}if((!f||b.debug)&&b.navPlugin.query().version){f=b.navPlugin.version}if((!f||b.debug)&&b.AXO.query().version){f=b.AXO.version}if(b.nonAppletDetectionOk(f)){c=f}if(!c||b.debug||a.VerifyTagsHas(2.2)||a.VerifyTagsHas(2.5)){e=b.lang.System.getProperty();if(e[0]){f=e[0];c=e[0];l=e[1]}}b.setPluginStatus(c,l,f);if(a.should_Insert_Query_Any()){e=a.insert_Query_Any();if(e[0]){c=e[0];l=e[1]}}b.setPluginStatus(c,l,f)},nonAppletDetectionOk:function(b){var d=this,e=d.$,a=d.navigator,c=1;if(!b||(!a.javaEnabled()&&!d.lang.System.getPropertyHas(b))||(!e.isIE&&!a.mimeObj&&!d.lang.System.getPropertyHas(b))||(e.isIE&&!e.ActiveXEnabled)){c=0}else{if(e.OS>=20){}else{if(d.info&&d.info.getPlugin2Status()<0&&d.info.BrowserRequiresPlugin2()){c=0}}}return c},setPluginStatus:function(d,f,a){var c=this,e=c.$,b;a=a||c.version0;if(c.OTF>0){d=d||c.lang.System.getProperty()[0]}if(c.OTF<3){b=d?1:(a?-0.2:-1);if(c.installed===null||b>c.installed){c.installed=b}}if(c.OTF==2&&c.NOTF&&!c.applet.getResult()[0]&&!c.lang.System.getProperty()[0]){c.installed=a?-0.2:-1};if(c.OTF==3&&c.installed!=-0.5&&c.installed!=0.5){c.installed=(c.NOTF.isJavaActive(1)==1||c.lang.System.getProperty()[0])?0.5:-0.5}if(c.OTF==4&&(c.installed==-0.5||c.installed==0.5)){if(d){c.installed=1}else{if(c.NOTF.isJavaActive(1)==1){if(a){c.installed=1;d=a}else{c.installed=0}}else{if(a){c.installed=-0.2}else{c.installed=-1}}}};if(a){c.version0=e.formatNum(e.getNum(a))}if(d){c.version=e.formatNum(e.getNum(d))}if(f&&e.isString(f)){c.vendor=f}if(!c.vendor){c.vendor=""}if(c.verify&&c.verify.isEnabled()){c.getVersionDone=0}else{if(c.getVersionDone!=1){if(c.OTF<2){c.getVersionDone=0}else{c.getVersionDone=c.applet.can_Insert_Query_Any()?0:1}}}},DTK:{$:1,hasRun:0,status:null,VERSIONS:[],version:"",HTML:null,Plugin2Status:null,classID:["clsid:CAFEEFAC-DEC7-0000-0001-ABCDEFFEDCBA","clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA"],mimeType:["application/java-deployment-toolkit","application/npruntime-scriptable-plugin;DeploymentToolkit"],disabled:function(){return this.$$.isDisabled.DTK()},query:function(){var k=this,g=k.$,d=k.$$,j,l,h,m={},f={},a,c=null,i=null,b=(k.hasRun||k.disabled());k.hasRun=1;if(b){return k}k.status=0;if(g.isIE&&g.verIE>=6){for(l=0;l0?1:-1;for(l=0;l=b.minIEver){i=a.search(j,j,d);if(i.length>0&&d){i=a.search(k,k,d)}}else{if(d){i=a.search(f,f,true)}if(i.length==0){i=a.search(h,g,false)}}if(i.length){a.version=i[0];a.VERSIONS=[].concat(i)};return a},search:function(a,j,p){var h,d,f=this,e=f.$,k=f.$$,n,c,l,q,b,o,r,i=[];if(e.compareNums(a.join(","),j.join(","))>0){j=a}j=e.formatNum(j.join(","));var m,s="1,4,2,0",g="JavaPlugin."+a[0]+""+a[1]+""+a[2]+""+(a[3]>0?("_"+(a[3]<10?"0":"")+a[3]):"");for(h=0;h=0;l--){r="JavaWebStart.isInstalled."+b+l+".0";if(e.compareNums(d[0]+","+d[1]+","+l+",0",j)>=0&&!e.getAXO(r)){continue}m=e.compareNums(d[0]+","+d[1]+","+l+",0",s)<0?true:false;for(q=d[3];q>=0;q--){c=l+"_"+(q<10?"0"+q:q);o=n+c;if(e.getAXO(o)&&(m||e.getAXO(r))){i.push(b+c);if(!p){return i}}if(o==g){return i}}if(e.getAXO(n+l)&&(m||e.getAXO(r))){i.push(b+l);if(!p){return i}}if(n+l==g){return i}}}return i}},navMime:{$:1,hasRun:0,mimetype:"",version:"",length:0,mimeObj:0,pluginObj:0,disabled:function(){return this.$$.isDisabled.navMime()},query:function(){var i=this,f=i.$,a=i.$$,b=(i.hasRun||i.disabled());i.hasRun=1;if(b){return i};var n=/^\s*application\/x-java-applet;jpi-version\s*=\s*(\d.*)$/i,g,l,j,d="",h="a",o,m,k={},c=f.formatNum("0");for(l=0;l0){c=g}}}g=k[h+c];if(g){o=f.hasMimeType(g);i.mimeObj=o;i.pluginObj=o?o.enabledPlugin:0;i.mimetype=g;i.version=c};return i}},navPlugin:{$:1,hasRun:0,version:"",disabled:function(){return this.$$.isDisabled.navPlugin()},query:function(){var m=this,e=m.$,c=m.$$,h=c.navigator,j,l,k,g,d,a,i,f=0,b=(m.hasRun||m.disabled());m.hasRun=1;if(b){return m};a=h.pluginObj.name||"";i=h.pluginObj.description||"";if(!f||c.debug){g=/Java.*TM.*Platform[^\d]*(\d+)(?:[\.,_](\d*))?(?:\s*[Update]+\s*(\d*))?/i;if((g.test(a)||g.test(i))&&parseInt(RegExp.$1,10)>=5){f="1,"+RegExp.$1+","+(RegExp.$2?RegExp.$2:"0")+","+(RegExp.$3?RegExp.$3:"0")}}if(!f||c.debug){g=/Java[^\d]*Plug-in/i;l=g.test(i)?e.formatNum(e.getNum(i)):0;k=g.test(a)?e.formatNum(e.getNum(a)):0;if(l&&(e.compareNums(l,e.formatNum("1,3"))<0||e.compareNums(l,e.formatNum("2"))>=0)){l=0}if(k&&(e.compareNums(k,e.formatNum("1,3"))<0||e.compareNums(k,e.formatNum("2"))>=0)){k=0}d=l&&k?(e.compareNums(l,k)>0?l:k):(l||k);if(d){f=d}}if(!f&&e.isSafari&&e.OS==2){j=e.findNavPlugin("Java.*\\d.*Plug-in.*Cocoa",0);if(j){l=e.getNum(j.description);if(l){f=l}}};if(f){m.version=e.formatNum(f)};return m}},lang:{$:1,System:{$:1,hasRun:0,result:[null,null],disabled:function(){return this.$$.isDisabled.windowDotJava()},getPropertyHas:function(a){var b=this,d=b.$,c=b.getProperty()[0];return(a&&c&&d.compareNums(d.formatNum(a),d.formatNum(c))===0)?1:0},getProperty:function(){var f=this,g=f.$,d=f.$$,i,h={},b=(f.hasRun||f.disabled());f.hasRun=1;if(!b){var a="java_qqq990";g[a]=null;try{var c=document.createElement("script");c.type="text/javascript";c.appendChild(document.createTextNode("(function(){var e;try{if (window.java && window.java.lang && window.java.lang.System){"+g.name+"."+a+'=[window.java.lang.System.getProperty("java.version")+" ",window.java.lang.System.getProperty("java.vendor")+" "]}}catch(e){}})();'));if(g.head.firstChild){g.head.insertBefore(c,g.head.firstChild)}else{g.head.appendChild(c)}g.head.removeChild(c)}catch(i){}if(g[a]&&g.isArray(g[a])){f.result=[].concat(g[a])}}return f.result}}},applet:{$:1,results:[[null,null],[null,null],[null,null]],getResult:function(){var c=this.results,a,b=[];for(a=0;a3){c[a]=3}b.allowed[a]=c[a]}}}},setVerifyTagsArray:function(d){var b=this,c=b.$,a=b.$$;if(a.getVersionDone===null){b.saveAsVerifyTagsArray(a.getVerifyTagsDefault())}if(a.debug||(a.verify&&a.verify.isEnabled())){b.saveAsVerifyTagsArray([3,3,3])}else{if(d){b.saveAsVerifyTagsArray(d)}}},allDisabled:function(){return this.$$.isDisabled.allApplets()},isDisabled:function(d){var b=this,c=b.$,a=b.$$;if(d==2&&!c.isIE){return 1}if(d===0||d==2){return a.isDisabled.ObjectTag()}if(d==1){return a.isDisabled.AppletTag()}},can_Insert_Query:function(b){var a=this;if(a.HTML[b]){return 0}return !a.isDisabled(b)},can_Insert_Query_Any:function(){var b=this,a;for(a=0;a=2||(b.allowed[a]==1&&!b.getResult()[0]))&&e.isAppletActive(a)>=0){return 1}}return 0},isJavaActive:function(d){var f=this,c=f.$$,a,b,e=-9;for(a=0;ae){e=b}}return e},isAppletActive:function(c,a){var d=this,b=d.$$.applet.active;if(!a){b[c]=d.isAppletActive_(c)}return b[c]},isAppletActive_:function(d){var g=this,f=g.$,b=g.$$,l=b.navigator,a=b.applet,h=a.HTML[d],i,k,c=0,j=f.getTagStatus(h,a.DummySpanTagHTML,a.DummyObjTagHTML,g.count);if(j==-2){return -2}try{if(f.isIE&&f.verIE>=b.minIEver&&f.getDOMobj(h).object){return 1}}catch(i){}for(k=0;k0){c=1}}if(j==1&&(f.isIE||((b.version0&&l.javaEnabled()&&l.mimeObj&&(h.tagName=="object"||c))||b.lang.System.getProperty()[0]))){return 1}if(j<0){return -1}return 0},winOnLoadQuery:function(c,d){var b=d.$$,a;if(b.OTF==3){a=d.queryAllApplets();d.queryCompleted(a[1],a[2])}},$$onIntervalQuery:function(d){var c=d.$,b=d.$$,a;if(b.OTF==3){a=d.queryAllApplets();if(!d.shouldContinueQuery()||(c.winLoaded&&d.count>d.countMax)){d.queryCompleted(a[1],a[2])}}d.count++;if(b.OTF==3){setTimeout(d.onIntervalQuery,d.intervalLength)}},queryAllApplets:function(){var g=this,f=g.$,e=g.$$,d=e.applet,b,a,c;for(b=0;b=4){return}b.OTF=4;var a=e.isJavaActive();b.setPluginStatus(c,f,0);if(b.funcs){d.callArray(b.funcs)}if(d.onDoneEmptyDiv){d.onDoneEmptyDiv()}}},zz:0},zz:0}};PluginDetect.initScript();
\ No newline at end of file
diff --git a/trunk/server/src/uds/static/js/jquery-1.7.1.js b/trunk/server/src/uds/static/js/jquery-1.7.1.js
new file mode 100644
index 00000000..8ccd0ea7
--- /dev/null
+++ b/trunk/server/src/uds/static/js/jquery-1.7.1.js
@@ -0,0 +1,9266 @@
+/*!
+ * jQuery JavaScript Library v1.7.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Nov 21 21:11:03 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.1",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!memory;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ marginDiv,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = " a";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = marginDiv = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ conMarginTop, ptlm, vb, style, html,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
+ vb = "visibility:hidden;border:0;";
+ style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
+ html = "" +
+ "";
+
+ container = document.createElement("div");
+ container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Figure out if the W3C box model works as expected
+ div.innerHTML = "";
+ div.style.width = div.style.paddingLeft = "1px";
+ jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+ }
+
+ div.style.cssText = ptlm + vb;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ body.removeChild( container );
+ div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, attr, name,
+ data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
+ attr = this[0].attributes;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ jQuery._data( this[0], "parsedAttrs", true );
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var self = jQuery( this ),
+ args = [ parts[0], value ];
+
+ self.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise();
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.prop );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ jQuery.attr( elem, name, "" );
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( rboolean.test( name ) && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /\bhover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on.call( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /", "" ],
+ legend: [ 1, "" ],
+ thead: [ 1, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+ col: [ 2, "" ],
+ area: [ 1, "" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize and
+
+ {% block script %}{% endblock %}
+
+
+
+
+
+
+ {% block content %}
+ Contenido
+ {% endblock %}
+
+
+
+
+
+