diff --git a/conductor/tests/.metadata/.log b/conductor/tests/.metadata/.log new file mode 100644 index 0000000..dcd7af8 --- /dev/null +++ b/conductor/tests/.metadata/.log @@ -0,0 +1,189 @@ + +!ENTRY com.aptana.shared_core 4 4 2013-04-05 14:34:01.030 +!MESSAGE Error when analyzing module forms +!STACK 0 +java.lang.RuntimeException: java.io.IOException: No such file or directory + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:254) + at org.python.pydev.core.DeltaSaver.addInsertCommand(DeltaSaver.java:293) + at org.python.pydev.editor.codecompletion.revisited.ModulesManagerWithBuild.doAddSingleModule(ModulesManagerWithBuild.java:112) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:895) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:718) + at org.python.pydev.editor.codecompletion.revisited.SystemModulesManager.getModule(SystemModulesManager.java:342) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.getModule(AbstractASTManager.java:448) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findModuleFromPath(AbstractASTManager.java:1430) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1321) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1200) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:186) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:167) + at com.python.pydev.analysis.visitors.Scope.addImportTokens(Scope.java:173) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.visitImportFrom(AbstractScopeAnalyzerVisitor.java:562) + at com.python.pydev.analysis.scopeanalysis.ScopeAnalyzerVisitor.visitImportFrom(ScopeAnalyzerVisitor.java:106) + at org.python.pydev.parser.jython.ast.ImportFrom.accept(ImportFrom.java:99) + at org.python.pydev.parser.jython.ast.Module.traverse(Module.java:86) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.traverse(AbstractScopeAnalyzerVisitor.java:189) + at org.python.pydev.parser.jython.ast.VisitorBase.visitModule(VisitorBase.java:10) + at org.python.pydev.parser.jython.ast.Module.accept(Module.java:79) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.getOccurrencesWithScopeAnalyzer(AbstractRenameRefactorProcess.java:257) + at com.python.pydev.refactoring.wizards.rename.PyRenameAnyLocalProcess.findReferencesToRenameOnLocalScope(PyRenameAnyLocalProcess.java:38) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.findReferencesToRename(AbstractRenameRefactorProcess.java:181) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:258) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:227) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.checkAnnotations(MarkOccurrencesJob.java:226) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.run(MarkOccurrencesJob.java:126) + at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) +Caused by: java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively(Native Method) + at java.io.File.createNewFile(File.java:947) + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:252) + ... 27 more + +!ENTRY com.aptana.shared_core 4 4 2013-04-05 15:21:20.739 +!MESSAGE Error when analyzing module urls +!STACK 0 +java.lang.RuntimeException: java.io.IOException: No such file or directory + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:254) + at org.python.pydev.core.DeltaSaver.addInsertCommand(DeltaSaver.java:293) + at org.python.pydev.editor.codecompletion.revisited.ModulesManagerWithBuild.doAddSingleModule(ModulesManagerWithBuild.java:112) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:895) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:718) + at org.python.pydev.editor.codecompletion.revisited.SystemModulesManager.getRelativeModule(SystemModulesManager.java:139) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.getModule(AbstractASTManager.java:446) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findModuleFromPath(AbstractASTManager.java:1437) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1289) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1200) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:186) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:167) + at com.python.pydev.analysis.visitors.Scope.addImportTokens(Scope.java:173) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.visitImportFrom(AbstractScopeAnalyzerVisitor.java:562) + at com.python.pydev.analysis.scopeanalysis.ScopeAnalyzerVisitor.visitImportFrom(ScopeAnalyzerVisitor.java:106) + at org.python.pydev.parser.jython.ast.ImportFrom.accept(ImportFrom.java:99) + at org.python.pydev.parser.jython.ast.Module.traverse(Module.java:86) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.traverse(AbstractScopeAnalyzerVisitor.java:189) + at org.python.pydev.parser.jython.ast.VisitorBase.visitModule(VisitorBase.java:10) + at org.python.pydev.parser.jython.ast.Module.accept(Module.java:79) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.getOccurrencesWithScopeAnalyzer(AbstractRenameRefactorProcess.java:257) + at com.python.pydev.refactoring.wizards.rename.PyRenameImportProcess.findReferencesToRenameOnLocalScope(PyRenameImportProcess.java:66) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.findReferencesToRename(AbstractRenameRefactorProcess.java:181) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:258) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:227) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.checkAnnotations(MarkOccurrencesJob.java:226) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.run(MarkOccurrencesJob.java:126) + at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) +Caused by: java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively(Native Method) + at java.io.File.createNewFile(File.java:947) + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:252) + ... 27 more + +!ENTRY com.aptana.shared_core 4 4 2013-04-05 15:22:51.347 +!MESSAGE Error when analyzing module views +!STACK 0 +java.lang.RuntimeException: java.io.IOException: No such file or directory + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:254) + at org.python.pydev.core.DeltaSaver.addInsertCommand(DeltaSaver.java:293) + at org.python.pydev.editor.codecompletion.revisited.ModulesManagerWithBuild.doAddSingleModule(ModulesManagerWithBuild.java:112) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:895) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:718) + at org.python.pydev.editor.codecompletion.revisited.SystemModulesManager.getModule(SystemModulesManager.java:342) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.getModule(AbstractASTManager.java:448) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findModuleFromPath(AbstractASTManager.java:1430) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1304) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1200) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:186) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:167) + at com.python.pydev.analysis.visitors.Scope.addImportTokens(Scope.java:173) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.visitImportFrom(AbstractScopeAnalyzerVisitor.java:562) + at com.python.pydev.analysis.scopeanalysis.ScopeAnalyzerVisitor.visitImportFrom(ScopeAnalyzerVisitor.java:106) + at org.python.pydev.parser.jython.ast.ImportFrom.accept(ImportFrom.java:99) + at org.python.pydev.parser.jython.ast.Module.traverse(Module.java:86) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.traverse(AbstractScopeAnalyzerVisitor.java:189) + at org.python.pydev.parser.jython.ast.VisitorBase.visitModule(VisitorBase.java:10) + at org.python.pydev.parser.jython.ast.Module.accept(Module.java:79) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.getOccurrencesWithScopeAnalyzer(AbstractRenameRefactorProcess.java:257) + at com.python.pydev.refactoring.wizards.rename.PyRenameImportProcess.findReferencesToRenameOnLocalScope(PyRenameImportProcess.java:66) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.findReferencesToRename(AbstractRenameRefactorProcess.java:181) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:258) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:227) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.checkAnnotations(MarkOccurrencesJob.java:226) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.run(MarkOccurrencesJob.java:126) + at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) +Caused by: java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively(Native Method) + at java.io.File.createNewFile(File.java:947) + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:252) + ... 27 more + +!ENTRY com.aptana.shared_core 4 4 2013-04-05 15:25:25.732 +!MESSAGE java.io.IOException: No such file or directory +!STACK 0 +java.lang.RuntimeException: java.io.IOException: No such file or directory + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:254) + at org.python.pydev.core.DeltaSaver.addInsertCommand(DeltaSaver.java:293) + at org.python.pydev.editor.codecompletion.revisited.ModulesManagerWithBuild.doAddSingleModule(ModulesManagerWithBuild.java:112) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:895) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:718) + at org.python.pydev.editor.codecompletion.revisited.SystemModulesManager.getModule(SystemModulesManager.java:342) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.getModule(AbstractASTManager.java:448) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findModuleFromPath(AbstractASTManager.java:1430) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1321) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1200) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:186) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:167) + at com.python.pydev.analysis.visitors.Scope.addImportTokens(Scope.java:173) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.visitImport(AbstractScopeAnalyzerVisitor.java:538) + at org.python.pydev.parser.jython.ast.Import.accept(Import.java:79) + at org.python.pydev.parser.jython.ast.Module.traverse(Module.java:86) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.traverse(AbstractScopeAnalyzerVisitor.java:189) + at org.python.pydev.parser.jython.ast.VisitorBase.visitModule(VisitorBase.java:10) + at org.python.pydev.parser.jython.ast.Module.accept(Module.java:79) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.getOccurrencesWithScopeAnalyzer(AbstractRenameRefactorProcess.java:257) + at com.python.pydev.refactoring.wizards.rename.PyRenameAnyLocalProcess.findReferencesToRenameOnLocalScope(PyRenameAnyLocalProcess.java:38) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.findReferencesToRename(AbstractRenameRefactorProcess.java:181) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:258) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:227) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.checkAnnotations(MarkOccurrencesJob.java:226) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.run(MarkOccurrencesJob.java:126) + at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) +Caused by: java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively(Native Method) + at java.io.File.createNewFile(File.java:947) + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:252) + ... 26 more + +!ENTRY com.aptana.shared_core 4 4 2013-04-05 15:26:00.341 +!MESSAGE Error when analyzing module workflows +!STACK 0 +java.lang.RuntimeException: java.io.IOException: No such file or directory + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:254) + at org.python.pydev.core.DeltaSaver.addInsertCommand(DeltaSaver.java:293) + at org.python.pydev.editor.codecompletion.revisited.ModulesManagerWithBuild.doAddSingleModule(ModulesManagerWithBuild.java:112) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:895) + at org.python.pydev.editor.codecompletion.revisited.ModulesManager.getModule(ModulesManager.java:718) + at org.python.pydev.editor.codecompletion.revisited.SystemModulesManager.getRelativeModule(SystemModulesManager.java:139) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.getModule(AbstractASTManager.java:446) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findModuleFromPath(AbstractASTManager.java:1437) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1289) + at org.python.pydev.editor.codecompletion.revisited.AbstractASTManager.findOnImportedMods(AbstractASTManager.java:1200) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:186) + at com.python.pydev.analysis.visitors.ImportChecker.visitImportToken(ImportChecker.java:167) + at com.python.pydev.analysis.visitors.Scope.addImportTokens(Scope.java:173) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.visitImportFrom(AbstractScopeAnalyzerVisitor.java:562) + at com.python.pydev.analysis.scopeanalysis.ScopeAnalyzerVisitor.visitImportFrom(ScopeAnalyzerVisitor.java:106) + at org.python.pydev.parser.jython.ast.ImportFrom.accept(ImportFrom.java:99) + at org.python.pydev.parser.jython.ast.Module.traverse(Module.java:86) + at com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor.traverse(AbstractScopeAnalyzerVisitor.java:189) + at org.python.pydev.parser.jython.ast.VisitorBase.visitModule(VisitorBase.java:10) + at org.python.pydev.parser.jython.ast.Module.accept(Module.java:79) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.getOccurrencesWithScopeAnalyzer(AbstractRenameRefactorProcess.java:257) + at com.python.pydev.refactoring.wizards.rename.PyRenameAnyLocalProcess.findReferencesToRenameOnLocalScope(PyRenameAnyLocalProcess.java:38) + at com.python.pydev.refactoring.wizards.rename.AbstractRenameRefactorProcess.findReferencesToRename(AbstractRenameRefactorProcess.java:181) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:258) + at com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint.checkFinalConditions(PyRenameEntryPoint.java:227) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.checkAnnotations(MarkOccurrencesJob.java:226) + at com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob.run(MarkOccurrencesJob.java:126) + at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) +Caused by: java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively(Native Method) + at java.io.File.createNewFile(File.java:947) + at org.python.pydev.core.DeltaSaver.addCommand(DeltaSaver.java:252) + ... 27 more diff --git a/tabula/tabula/tabula/__init__.py b/tabula/tabula/tabula/__init__.py new file mode 100644 index 0000000..eb97e8c --- /dev/null +++ b/tabula/tabula/tabula/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/tabula/tabula/tabula/api.py b/tabula/tabula/tabula/api.py new file mode 100644 index 0000000..0c11460 --- /dev/null +++ b/tabula/tabula/tabula/api.py @@ -0,0 +1,210 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from portasclient.v1.client import Client as glazier_client + +log = logging.getLogger(__name__) + + +def glazierclient(request): + url = "http://127.0.0.1:8082" + log.debug('glazierclient connection created using token "%s" and url "%s"' + % (request.user.token, url)) + return glazier_client(endpoint=url, token=request.user.token.token['id']) + + +def datacenters_create(request, parameters): + env = glazierclient(request).environments.create(parameters.get('name', '')) + log.debug('Environment::Create {0}'.format(env)) + return env + + +def datacenters_delete(request, datacenter_id): + result = glazierclient(request).environments.delete(datacenter_id) + log.debug('Environment::Delete Id:{0}'.format(datacenter_id)) + return result + + +def datacenters_get(request, datacenter_id): + env = glazierclient(request).environments.get(datacenter_id) + log.debug('Environment::Get {0}'.format(env)) + return env + + +def datacenters_list(request): + log.debug('Environment::List') + return glazierclient(request).environments.list() + + +def datacenters_deploy(request, datacenter_id): + sessions = glazierclient(request).sessions.list(datacenter_id) + for session in sessions: + if session.state == 'open': + session_id = session.id + if not session_id: + return "Sorry, nothing to deploy." + log.debug('Obtained session with Id: {0}'.format(session_id)) + result = glazierclient(request).sessions.deploy(datacenter_id, session_id) + log.debug('Environment with Id: {0} deployed in session ' + 'with Id: {1}'.format(datacenter_id, session_id)) + return result + + +def services_create(request, environment_id, parameters): + session_id = None + sessions = glazierclient(request).sessions.list(environment_id) + + for s in sessions: + if s.state == 'open': + session_id = s.id + else: + glazierclient(request).sessions.delete(environment_id, s.id) + + if session_id is None: + session_id = glazierclient(request).sessions.configure(environment_id).id + + if parameters['service_type'] == 'Active Directory': + service = glazierclient(request)\ + .activeDirectories\ + .create(environment_id, session_id, parameters) + else: + service = glazierclient(request)\ + .webServers.create(environment_id, session_id, parameters) + + log.debug('Service::Create {0}'.format(service)) + return service + + +def get_time(obj): + return obj.updated + + +def services_list(request, datacenter_id): + services = [] + session_id = None + sessions = glazierclient(request).sessions.list(datacenter_id) + for s in sessions: + session_id = s.id + + if session_id: + services = glazierclient(request).activeDirectories.list(datacenter_id, + session_id) + services += glazierclient(request).webServers.list(datacenter_id, + session_id) + for i in range(len(services)): + reports = glazierclient(request).sessions. \ + reports(datacenter_id, session_id, + services[i].id) + + for report in reports: + services[i].operation = report.text + + log.debug('Service::List') + return services + + +def get_active_directories(request, datacenter_id): + services = [] + session_id = None + sessions = glazierclient(request).sessions.list(datacenter_id) + + for s in sessions: + session_id = s.id + + if session_id: + services = glazierclient(request)\ + .activeDirectories\ + .list(datacenter_id, session_id) + + log.debug('Service::Active Directories::List') + return services + + +def services_get(request, datacenter_id, service_id): + services = services_list(request, datacenter_id) + + for service in services: + if service.id == service_id: + log.debug('Service::Get {0}'.format(service)) + return service + + +def get_data_center_id_for_service(request, service_id): + datacenters = datacenters_list(request) + + for dc in datacenters: + services = services_list(request, dc.id) + for service in services: + if service.id == service_id: + return dc.id + + +def get_service_datails(request, service_id): + datacenters = datacenters_list(request) + services = [] + for dc in datacenters: + services += services_list(request, dc.id) + + for service in services: + if service.id == service_id: + return service + + +def get_status_message_for_service(request, service_id): + environment_id = get_data_center_id_for_service(request, service_id) + session_id = None + sessions = glazierclient(request).sessions.list(environment_id) + + for s in sessions: + session_id = s.id + + if session_id: + reports = glazierclient(request).sessions.\ + reports(environment_id, session_id, service_id) + + result = 'Initialization.... \n' + for report in reports: + result += ' ' + str(report.text) + '\n' + + return result + + +def services_delete(request, datacenter_id, service_id): + log.debug('Service::Remove EnvId: {0} ' + 'SrvId: {1}'.format(datacenter_id, service_id)) + + services = services_list(request, datacenter_id) + + session_id = None + sessions = glazierclient(request).sessions.list(datacenter_id) + for session in sessions: + if session.state == 'open': + session_id = session.id + + if session_id is None: + raise Exception("Sorry, you can not delete this service now.") + + for service in services: + if service.id is service_id: + if service.type is 'Active Directory': + glazierclient(request).activeDirectories.delete(datacenter_id, + session_id, + service_id) + elif service.type is 'IIS': + glazierclient(request).webServers.delete(datacenter_id, + session_id, + service_id) diff --git a/tabula/tabula/tabula/forms.py b/tabula/tabula/tabula/forms.py new file mode 100644 index 0000000..0e56d9c --- /dev/null +++ b/tabula/tabula/tabula/forms.py @@ -0,0 +1,120 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import string + +from django import forms +from django.utils.translation import ugettext_lazy as _ +import re +from tabula.tabula import api + +log = logging.getLogger(__name__) + + +class PasswordField(forms.CharField): + # Setup the Field + def __init__(self, label, *args, **kwargs): + super(PasswordField, self).__init__(min_length=7, required=True, + label=label, + widget=forms.PasswordInput( + render_value=False), + *args, **kwargs) + + def clean(self, value): + + # Setup Our Lists of Characters and Numbers + characters = list(string.letters) + special_characters = '!@#$%^&*()_+|\/.,~?><:{}' + numbers = [str(i) for i in range(10)] + + # Assume False until Proven Otherwise + numCheck = False + charCheck = False + specCharCheck = False + + # Loop until we Match + for char in value: + if not charCheck: + if char in characters: + charCheck = True + if not specCharCheck: + if char in special_characters: + specCharCheck = True + if not numCheck: + if char in numbers: + numCheck = True + if numCheck and charCheck and specCharCheck: + break + + if not numCheck or not charCheck or not specCharCheck: + raise forms.ValidationError(u'Your password must include at least \ + one letter, at least one number and \ + at least one special character.') + + return super(PasswordField, self).clean(value) + + +class WizardFormServiceType(forms.Form): + service = forms.ChoiceField(label=_('Service Type'), + choices=[ + ('Active Directory', 'Active Directory'), + ('IIS', 'Internet Information Services') + ]) + + +class WizardFormConfiguration(forms.Form): + 'The functions for this class will dynamically create in views.py' + pass + + +class WizardFormADConfiguration(forms.Form): + dc_name = forms.CharField(label=_('Domain Name'), + required=True) + + dc_count = forms.IntegerField(label=_('Instances Count'), + required=True, + min_value=1, + max_value=100, + initial=1) + + adm_password = PasswordField(_('Administrator password')) + + recovery_password = PasswordField(_('Recovery password')) + + def __init__(self, request, *args, **kwargs): + super(WizardFormADConfiguration, self).__init__(*args, **kwargs) + + +class WizardFormIISConfiguration(forms.Form): + iis_name = forms.CharField(label=_('IIS Server Name'), + required=True) + + adm_password = PasswordField(_('Administrator password')) + + iis_domain = forms.ChoiceField(label=_('Member of the Domain'), + required=False) + + def __init__(self, request, *args, **kwargs): + super(WizardFormIISConfiguration, self).__init__(*args, **kwargs) + + link = request.__dict__['META']['HTTP_REFERER'] + datacenter_id = re.search('tabula/(\S+)', link).group(0)[6:-1] + + domains = api.get_active_directories(request, datacenter_id) + + self.fields['iis_domain'].choices = [("", "")] + \ + [(domain.name, domain.name) + for domain in domains] diff --git a/tabula/tabula/tabula/overrides.py b/tabula/tabula/tabula/overrides.py new file mode 100644 index 0000000..849eaef --- /dev/null +++ b/tabula/tabula/tabula/overrides.py @@ -0,0 +1,21 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import horizon + +from panel import tabula + +project = horizon.get_dashboard('project') +project.register(tabula) diff --git a/tabula/tabula/tabula/panel.py b/tabula/tabula/tabula/panel.py new file mode 100644 index 0000000..6acae86 --- /dev/null +++ b/tabula/tabula/tabula/panel.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import horizon +from django.utils.translation import ugettext_lazy as _ +from openstack_dashboard.dashboards.project import dashboard + + +class tabula(horizon.Panel): + name = _("Environments") + slug = 'tabula' + + +dashboard.Project.register(tabula) diff --git a/tabula/tabula/tabula/tables.py b/tabula/tabula/tabula/tables.py new file mode 100644 index 0000000..67856b4 --- /dev/null +++ b/tabula/tabula/tabula/tables.py @@ -0,0 +1,193 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import re + +from django.utils.translation import ugettext_lazy as _ +from horizon import messages +from horizon import tables +from tabula.tabula import api + + +LOG = logging.getLogger(__name__) + + +class CreateService(tables.LinkAction): + name = 'CreateService' + verbose_name = _('Create Service') + url = 'horizon:project:tabula:create' + classes = ('btn-launch', 'ajax-modal') + + def allowed(self, request, datum): + return True + + def action(self, request, service): + api.services_create(request, service) + + +class CreateEnvironment(tables.LinkAction): + name = 'CreateEnvironment' + verbose_name = _('Create Environment') + url = 'horizon:project:tabula:create_dc' + classes = ('btn-launch', 'ajax-modal') + + def allowed(self, request, datum): + return True + + def action(self, request, environment): + api.datacenters_create(request, environment) + + +class DeleteEnvironment(tables.BatchAction): + name = 'delete' + action_present = _('Delete') + action_past = _('Delete') + data_type_singular = _('Environment') + data_type_plural = _('Environment') + classes = ('btn-danger', 'btn-terminate') + + def allowed(self, request, datum): + return True + + def action(self, request, environment_id): + api.datacenters_delete(request, environment_id) + + +class DeleteService(tables.BatchAction): + name = 'delete' + action_present = _('Delete') + action_past = _('Delete') + data_type_singular = _('Service') + data_type_plural = _('Service') + classes = ('btn-danger', 'btn-terminate') + + def allowed(self, request, datum): + return True + + def action(self, request, service_id): + link = request.__dict__['META']['HTTP_REFERER'] + datacenter_id = re.search('tabula/(\S+)', link).group(0)[6:-1] + + try: + api.services_delete(request, datacenter_id, service_id) + except: + messages.error(request, _('Sorry, you can not delete this ' + 'service right now.')) + + +class DeployEnvironment(tables.BatchAction): + name = 'deploy' + action_present = _('Deploy') + action_past = _('Deploy') + data_type_singular = _('Environment') + data_type_plural = _('Environment') + classes = 'btn-launch' + + def allowed(self, request, datum): + return True + + def action(self, request, environment_id): + return api.datacenters_deploy(request, environment_id) + + +class ShowDataCenterServices(tables.LinkAction): + name = 'edit' + verbose_name = _('Services') + url = 'horizon:project:tabula:services' + + def allowed(self, request, instance): + return True + + +class UpdateDCRow(tables.Row): + ajax = True + + def get_data(self, request, datacenter_id): + return api.datacenters_get(request, datacenter_id) + + +class UpdateServiceRow(tables.Row): + ajax = True + + def get_data(self, request, service_id): + + link = request.__dict__['META']['HTTP_REFERER'] + datacenter_id = re.search('tabula/(\S+)', link).group(0)[6:-1] + + service = api.services_get(request, datacenter_id, service_id) + + return service + + +STATUS_DISPLAY_CHOICES = ( + ('draft', 'Ready to deploy'), + ('pending', 'Wait for configuration'), + ('inprogress', 'Deploy in progress'), + ('finished', 'Active') +) + + +class DCTable(tables.DataTable): + STATUS_CHOICES = ( + (None, True), + ('Ready to deploy', True), + ('Active', True) + ) + + name = tables.Column('name', + link=('horizon:project:tabula:services'), + verbose_name=_('Name')) + + status = tables.Column('status', verbose_name=_('Status'), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + + class Meta: + name = 'tabula' + verbose_name = _('Environment') + row_class = UpdateDCRow + status_columns = ['status'] + table_actions = (CreateDataCenter,) + row_actions = (ShowDataCenterServices, DeleteDataCenter, + DeployDataCenter) + + +class ServicesTable(tables.DataTable): + STATUS_CHOICES = ( + (None, True), + ('Ready to deploy', True), + ('Active', True) + ) + + name = tables.Column('name', verbose_name=_('Name'), + link=('horizon:project:tabula:service_details')) + + _type = tables.Column('service_type', verbose_name=_('Type')) + + status = tables.Column('status', verbose_name=_('Status'), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + + operation = tables.Column('operation', verbose_name=_('Operation')) + + class Meta: + name = 'services' + verbose_name = _('Services') + row_class = UpdateServiceRow + status_columns = ['status'] + table_actions = (CreateService,) diff --git a/tabula/tabula/tabula/tabs.py b/tabula/tabula/tabula/tabs.py new file mode 100644 index 0000000..d47bbd8 --- /dev/null +++ b/tabula/tabula/tabula/tabs.py @@ -0,0 +1,58 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs +import logging + +from tabula.tabula import api + + +LOG = logging.getLogger(__name__) + + +class OverviewTab(tabs.Tab): + name = _("Service") + slug = "_service" + template_name = '_services.html' + + def get_context_data(self, request): + data = self.tab_group.kwargs['service'] + + return {"service_name": data.name, + "service_status": data.status, + "service_type": data.service_type, + "service_domain": data.domain} + + +class LogsTab(tabs.Tab): + name = _("Logs") + slug = "_logs" + template_name = '_service_logs.html' + + def get_context_data(self, request): + data = self.tab_group.kwargs['service'] + + reports = api.get_status_message_for_service(request, data.id) + + return {"reports": reports} + + +class ServicesTabs(tabs.TabGroup): + slug = "services_details" + tabs = (OverviewTab, LogsTab) + sticky = True diff --git a/tabula/tabula/tabula/urls.py b/tabula/tabula/tabula/urls.py new file mode 100644 index 0000000..d4076d2 --- /dev/null +++ b/tabula/tabula/tabula/urls.py @@ -0,0 +1,36 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.conf.urls.defaults import patterns, url + +from .views import IndexView, Services, CreateDCView, DetailServiceView +from .views import Wizard +from .forms import WizardFormServiceType, WizardFormConfiguration + +VIEW_MOD = 'openstack_dashboard.dashboards.project.tabula.views' + +urlpatterns = patterns(VIEW_MOD, + url(r'^$', IndexView.as_view(), name='index'), + url(r'^create$', + Wizard.as_view([WizardFormServiceType, + WizardFormConfiguration]), + name='create'), + url(r'^create_dc$', CreateDCView.as_view(), + name='create_dc'), + url(r'^(?P[^/]+)/$', + Services.as_view(), name='services'), + url(r'^(?P[^/]+)/details$', + DetailServiceView.as_view(), + name='service_details')) diff --git a/tabula/tabula/tabula/views.py b/tabula/tabula/tabula/views.py new file mode 100644 index 0000000..bcd28c5 --- /dev/null +++ b/tabula/tabula/tabula/views.py @@ -0,0 +1,205 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import re + +from django.views import generic +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.contrib.formtools.wizard.views import SessionWizardView + +from horizon import exceptions +from horizon import tabs +from horizon import tables +from horizon import workflows +from horizon.forms.views import ModalFormMixin + +from tabula.tabula import api + +from .tables import DCTable, ServicesTable +from .workflows import CreateDC +from .tabs import ServicesTabs +from .forms import (WizardFormADConfiguration, WizardFormIISConfiguration) + +from horizon import messages + +from django.http import HttpResponseRedirect + +LOG = logging.getLogger(__name__) + + +class Wizard(ModalFormMixin, SessionWizardView, generic.FormView): + template_name = 'services_tabs.html' + + def done(self, form_list, **kwargs): + link = self.request.__dict__['META']['HTTP_REFERER'] + datacenter_id = re.search('tabula/(\S+)', link).group(0)[6:-1] + + url = "/project/tabula/%s/" % datacenter_id + + service_type = form_list[0].data.get('0-service', '') + parameters = {'service_type': service_type} + data = form_list[1].data + if service_type == 'Active Directory': + parameters['configuration'] = 'standalone' + parameters['name'] = str(data.get('1-dc_name', 'noname')) + parameters['domain'] = parameters['name'] # Fix Me in orchestrator + parameters['adminPassword'] = str(data.get('1-adm_password', '')) + dc_count = int(data.get('1-dc_count', 1)) + recovery_password = str(data.get('1-recovery_password', '')) + parameters['units'] = [] + parameters['units'].append({'isMaster': True, + 'recoveryPassword': recovery_password, + 'location': 'west-dc'}) + for dc in range(dc_count - 1): + parameters['units'].append({ + 'isMaster': False, + 'recoveryPassword': recovery_password, + 'location': 'west-dc' + }) + + elif service_type == 'IIS': + password = data.get('1-adm_password', '') + parameters['name'] = str(data.get('1-iis_name', 'noname')) + parameters['credentials'] = {'username': 'Administrator', + 'password': password} + parameters['domain'] = str(data.get('1-iis_domain', '')) + password = form_list[1].data.get('1-adm_password', '') + domain = form_list[1].data.get('1-iis_domain', '') + dc_user = form_list[1].data.get('1-domain_user_name', '') + dc_pass = form_list[1].data.get('1-domain_user_password', '') + parameters['name'] = str(form_list[1].data.get('1-iis_name', + 'noname')) + parameters['domain'] = parameters['name'] + parameters['credentials'] = {'username': 'Administrator', + 'password': password} + parameters['domain'] = str(domain) + parameters['location'] = 'west-dc' + + parameters['units'] = [] + parameters['units'].append({'id': '1', + 'endpoint': [{'host': '10.0.0.1'}], + 'location': 'west-dc'}) + + service = api.services_create(self.request, datacenter_id, parameters) + + message = "The %s service successfully created." % service_type + messages.success(self.request, message) + return HttpResponseRedirect(url) + + def get_form(self, step=None, data=None, files=None): + + form = super(Wizard, self).get_form(step, data, files) + if data: + self.service_type = data.get('0-service', '') + if self.service_type == 'Active Directory': + self.form_list['1'] = WizardFormADConfiguration + elif self.service_type == 'IIS': + self.form_list['1'] = WizardFormIISConfiguration + + return form + + def get_form_kwargs(self, step=None): + return {'request': self.request} if step == u'1' else {} + + def get_form_step_data(self, form): + LOG.debug(form.data) + return form.data + + def get_context_data(self, form, **kwargs): + context = super(Wizard, self).get_context_data(form=form, **kwargs) + if self.steps.index > 0: + context.update({'service_type': self.service_type}) + return context + + +class IndexView(tables.DataTableView): + table_class = WinDCTable + template_name = 'index.html' + + def get_data(self): + try: + data_centers = api.datacenters_list(self.request) + except: + data_centers = [] + exceptions.handle(self.request, + _('Unable to retrieve data centers list.')) + return data_centers + + +class Services(tables.DataTableView): + table_class = ServicesTable + template_name = 'services.html' + + def get_context_data(self, **kwargs): + context = super(Services, self).get_context_data(**kwargs) + context['dc_name'] = self.dc_name + return context + + def get_data(self): + try: + dc_id = self.kwargs['data_center_id'] + self.datacenter_id = dc_id + datacenter = api.datacenters_get(self.request, dc_id) + self.dc_name = datacenter.name + services = api.services_list(self.request, dc_id) + except: + services = [] + exceptions.handle(self.request, + _('Unable to retrieve list of services for ' + 'data center "%s".') % self.dc_name) + self._services = services + return self._services + + +class DetailServiceView(tabs.TabView): + tab_group_class = ServicesTabs + template_name = 'service_details.html' + + def get_context_data(self, **kwargs): + context = super(DetailServiceView, self).get_context_data(**kwargs) + context["service"] = self.get_data() + context["service_name"] = self.get_data().name + return context + + def get_data(self): + if not hasattr(self, "_service"): + try: + service_id = self.kwargs['service_id'] + service = api.get_service_datails(self.request, service_id) + except: + redirect = reverse('horizon:project:tabula:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'service "%s".') % service_id, + redirect=redirect) + self._service = service + return self._service + + def get_tabs(self, request, *args, **kwargs): + service = self.get_data() + return self.tab_group_class(request, service=service, **kwargs) + + +class CreateDCView(workflows.WorkflowView): + workflow_class = CreateDC + template_name = 'create_dc.html' + + def get_initial(self): + initial = super(CreateDCView, self).get_initial() + initial['project_id'] = self.request.user.tenant_id + initial['user_id'] = self.request.user.id + return initial diff --git a/tabula/tabula/tabula/workflows.py b/tabula/tabula/tabula/workflows.py new file mode 100644 index 0000000..f767c95 --- /dev/null +++ b/tabula/tabula/tabula/workflows.py @@ -0,0 +1,96 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import re + +from django.utils.text import normalize_newlines +from django.utils.translation import ugettext as _ + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from tabula.tabula import api + + +LOG = logging.getLogger(__name__) + + +class SelectProjectUserAction(workflows.Action): + project_id = forms.ChoiceField(label=_("Project")) + user_id = forms.ChoiceField(label=_("User")) + + def __init__(self, request, *args, **kwargs): + super(SelectProjectUserAction, self).__init__(request, *args, **kwargs) + # Set our project choices + projects = [(tenant.id, tenant.name) + for tenant in request.user.authorized_tenants] + self.fields['project_id'].choices = projects + + # Set our user options + users = [(request.user.id, request.user.username)] + self.fields['user_id'].choices = users + + class Meta: + name = _("Project & User") + # Unusable permission so this is always hidden. However, we + # keep this step in the workflow for validation/verification purposes. + permissions = ("!",) + + +class SelectProjectUser(workflows.Step): + action_class = SelectProjectUserAction + + +class ConfigureDCAction(workflows.Action): + name = forms.CharField(label=_("Environment Name"), required=True) + + class Meta: + name = _("Environment") + help_text_template = "_data_center_help.html" + + +class ConfigureDC(workflows.Step): + action_class = ConfigureDCAction + contibutes = ('name',) + + def contribute(self, data, context): + if data: + context['name'] = data.get('name', '') + return context + + +class CreateDC(workflows.Workflow): + slug = "create" + name = _("Create Environment") + finalize_button_name = _("Create") + success_message = _('Created environment "%s".') + failure_message = _('Unable to create environment "%s".') + success_url = "horizon:project:tabula:index" + default_steps = (SelectProjectUser, ConfigureDC) + + def format_status_message(self, message): + name = self.context.get('name', 'noname') + return message % name + + def handle(self, request, context): + try: + datacenter = api.datacenters_create(request, context) + return True + except: + exceptions.handle(request) + return False