Commit 3dc93ded by Sulyok Gabor

Changes and fixes for deployment

parent 97c4dbde
Pipeline #274 failed with stage
in 0 seconds

15.9 KB | W: | H:

15.9 KB | W: | H:

circle/media/setty/apache.jpg
circle/media/setty/apache.jpg
circle/media/setty/apache.jpg
circle/media/setty/apache.jpg
  • 2-up
  • Swipe
  • Onion skin

9.07 KB | W: | H:

9.07 KB | W: | H:

circle/media/setty/mysql.jpg
circle/media/setty/mysql.jpg
circle/media/setty/mysql.jpg
circle/media/setty/mysql.jpg
  • 2-up
  • Swipe
  • Onion skin

19.9 KB | W: | H:

19.9 KB | W: | H:

circle/media/setty/wordpress.jpg
circle/media/setty/wordpress.jpg
circle/media/setty/wordpress.jpg
circle/media/setty/wordpress.jpg
  • 2-up
  • Swipe
  • Onion skin
from .models import * from models import ElementTemplate, Machine, Service, ServiceNode, Element, ElementConnection
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Q from django.db.models import Q
from django.db.models.loading import get_model from django.db.models.loading import get_model
from django.db import transaction from django.db import transaction
from saltstackhelper import * from saltstackhelper import SaltStackHelper
import os
from vm.models import Instance from vm.models import Instance
import logging import logging
logger = logging.getLogger(__name__)
salthelper = SaltStackHelper()
class SettyController: class SettyController:
salthelper = SaltStackHelper() salthelper = SaltStackHelper()
'''
Save the service data given by the designer. It includes all
Machines, ServiceNodes and connections between them. If the
saving fails, the last saved status will not be lost.
'''
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def saveService(serviceId, serviceName, serviceNodes, machines, elementConnections): def saveService(serviceId, serviceName, serviceNodes, machines, elementConnections):
...@@ -18,7 +24,7 @@ class SettyController: ...@@ -18,7 +24,7 @@ class SettyController:
try: try:
service = Service.objects.get(id=serviceId) service = Service.objects.get(id=serviceId)
except Service.DoesNotExist: except Service.DoesNotExist:
return JsonResponse({'error': 'Service not found'}) return {'status': 'error', 'errors': 'SERVICE_NOT_FOUND'}
service.name = serviceName service.name = serviceName
service.save() service.save()
...@@ -61,16 +67,21 @@ class SettyController: ...@@ -61,16 +67,21 @@ class SettyController:
connectionObject.save() connectionObject.save()
return {"serviceName": serviceName} return {'status': 'success', 'serviceName': serviceName}
'''
Load all ServiceNode derivatives, ElementConnections and saved Machines
for the Service with the given id and give it back to the caller.
If the service doesn't exists, error is returned.
'''
@staticmethod @staticmethod
def loadService(serviceId): def loadService(serviceId):
service = None service = None
try: try:
service = Service.objects.get(id=serviceId) service = Service.objects.get(id=serviceId)
except Service.DoesNotExist: except Service.DoesNotExist:
return JsonResponse({'error': 'Service not found'}) return {'status': 'error', 'errors': 'SERVICE_NOT_FOUND'}
machineList = Machine.objects.filter(service=service) machineList = Machine.objects.filter(service=service)
serviceNodes = [] serviceNodes = []
...@@ -95,6 +106,12 @@ class SettyController: ...@@ -95,6 +106,12 @@ class SettyController:
'serviceNodes': serviceNodes, 'serviceNodes': serviceNodes,
'machines': machines} 'machines': machines}
'''
Queiry information -- field names, type of that field -- about
an ElemenTemplate or Machine. If the ElementTemplate doesn't exist
or the prototype couldn't be retrieved
'''
@staticmethod @staticmethod
def getInformation(elementTemplateId, hostname): def getInformation(elementTemplateId, hostname):
if elementTemplateId: if elementTemplateId:
...@@ -104,19 +121,24 @@ class SettyController: ...@@ -104,19 +121,24 @@ class SettyController:
model = get_model('setty', elementTemplate.prototype) model = get_model('setty', elementTemplate.prototype)
return model.getInformation() return model.getInformation()
except ElementTemplate.DoesNotExist: except ElementTemplate.DoesNotExist:
return return {'status': 'error', 'errors': 'ELEMENTTEMPLATE_DOESNT_EXISTS'}
except LookupError: except LookupError:
return return {'status': 'error', 'errors': 'ELEMENTTEMPLATE_COULDNT_GET_PROTOTYPE'}
elif hostname: elif hostname:
return Machine.getInformation() return Machine.getInformation()
elif hostname and elementTemplateId: elif hostname and elementTemplateId:
raise PermissionDenied # TODO: something more meaningful return {'status': 'error', 'errors': 'BOTH_ELEMENTEMPLATE_HOSTNAME_FILLED'}
else: else:
raise PermissionDenied # TODO: something more meaningful return {'status': 'error', 'errors': 'UNKNOWN_ERROR'}
'''
Return the available hostnames to add to the Service
based on already used / saved hostanames and Instances
owned by current user which is also known by SaltStack
'''
@staticmethod @staticmethod
def getMachineAvailableList(serviceId, usedHostnames, current_user): def getMachineAvailableList(serviceId, usedHostnames, current_user):
saltMinions = SettyController.salthelper.getAllMinionsUngrouped()
savedMachines = Machine.objects.filter(service=serviceId) savedMachines = Machine.objects.filter(service=serviceId)
savedHostNames = [] savedHostNames = []
...@@ -131,27 +153,41 @@ class SettyController: ...@@ -131,27 +153,41 @@ class SettyController:
userMachines.append(instance.vm_name) userMachines.append(instance.vm_name)
usedHostnamesByUser = set(savedHostNames + usedHostnames) usedHostnamesByUser = set(savedHostNames + usedHostnames)
availableInstances = set(set(userMachines) - usedHostnamesByUser)
saltMinions = SettyController.salthelper.getAllMinionsUngrouped()
if not usedHostnamesByUser: if not usedHostnamesByUser:
return {'machinedata': [machineName for machineName in userMachines if machineName in saltMinions]}
return {'machinedata': [machineName for machineName in userMachines if machineName in saltMinions] } return {'machinedata': [machineName for machineName in availableInstances if machineName in saltMinions]}
availableInstances = list(set(userMachines) - usedHostnamesByUser) '''
return {'machinedata': [ machineName for machineName in availableInstances if machineName in saltMinions ]} Add a machine with the given hostname to the Service. If there is a
saved Machine with the given hostname or the SaltStack doesn't know about
the machine give an error back. If it exists and not already saved give
back the new Machine instance
'''
#TODO: addMachine requires usedHostnames too for safety
@staticmethod @staticmethod
def addMachine(hostname): def addMachine(hostname):
try: try:
Machine.objects.get(hostname=hostname) Machine.objects.get(hostname=hostname)
return {'error': 'already added or doesnt exists'}
except: except:
pass return {'status': 'error', 'errors': 'MACHINE_ALREADY_ADDED'}
if SettyController.salthelper.checkMinionExists(hostname): if SettyController.salthelper.checkMinionExists(hostname):
machine = Machine.clone() machine = Machine.clone()
machine.hostname = hostname machine.hostname = hostname
return machine.getDataDictionary() return machine.getDataDictionary()
else: else:
return {'error': 'already added or doesnt exists'} return {'status': 'error', 'errors': 'MACHINE_DOESNT_EXISTS'}
'''
Add a new service node based on the given ElementTemplate ID to
the current service and return the data member of it.
If no ElementTemplate exists with the given ID or it couldn't be
institiated give an error.
'''
@staticmethod @staticmethod
def addServiceNode(elementTemplateId): def addServiceNode(elementTemplateId):
...@@ -162,12 +198,13 @@ class SettyController: ...@@ -162,12 +198,13 @@ class SettyController:
model = get_model('setty', elementTemplate.prototype) model = get_model('setty', elementTemplate.prototype)
return model.clone().getDataDictionary() return model.clone().getDataDictionary()
except ElementTemplate.DoesNotExist: except ElementTemplate.DoesNotExist:
return {'error': "ElementTemplate doesn't exists"} return {'status': 'error', 'errors': 'ELEMENTTEMPLATE_DOESNT_EXISTS'}
except: except:
return {'error': 'Can not get prototype'} return {'status': 'error', 'errors': 'ELEMENTTEMPLATE_COULDNT_GET_PROTOTYPE'}
else: else:
return {'error': 'templateid'} return {'status': 'error', 'errors': 'INVALID_ELEMENTTEMPLATE_ID'}
''' Deploy a service using SaltStack. The steps are described inline.'''
@staticmethod @staticmethod
def deploy(serviceId): def deploy(serviceId):
service = Service.objects.get(id=serviceId) service = Service.objects.get(id=serviceId)
...@@ -177,7 +214,7 @@ class SettyController: ...@@ -177,7 +214,7 @@ class SettyController:
nodesToBeDeployed = [] nodesToBeDeployed = []
for serviceNode in serviveNodeList: for serviceNode in serviveNodeList:
castedServiceNode = serviceNode.cast() castedServiceNode = serviceNode.cast()
nodesToBeDeployed.append( castedServiceNode ) nodesToBeDeployed.append(castedServiceNode)
errorMessage = castedServiceNode.checkDependenciesAndAttributes() errorMessage = castedServiceNode.checkDependenciesAndAttributes()
if errorMessage: if errorMessage:
errorMessages.append(errorMessage) errorMessages.append(errorMessage)
...@@ -186,7 +223,8 @@ class SettyController: ...@@ -186,7 +223,8 @@ class SettyController:
return {'status': 'error', return {'status': 'error',
'errors': errorMessages} 'errors': errorMessages}
# phase one: ask the servicenodes to generate their needed salt commands # phase one: ask the servicenodes to generate their needed salt
# commands
for serviceNode in nodesToBeDeployed: for serviceNode in nodesToBeDeployed:
serviceNode.generateSaltCommands() serviceNode.generateSaltCommands()
...@@ -196,17 +234,19 @@ class SettyController: ...@@ -196,17 +234,19 @@ class SettyController:
nodesToBeDeployed.sort(reverse=True) nodesToBeDeployed.sort(reverse=True)
# dbgCheck = [] dbgCheck = []
# for node in nodesToBeDeployed: # for node in nodesToBeDeployed:
# commandArray = [] # commandArray = []
#
# for command in node.generatedCommands: # for command in node.generatedCommands:
# logger.error( "salt '"+ command.hostname +"' state.sls " + command.command + " pillar=\"" + str(command.parameters) + '"' )
# commandArray.append( command.toDict() ) # commandArray.append( command.toDict() )
# #
# dbgCheck.append({ "nodeName": str(node.__class__.__name__), # dbgCheck.append({ "nodeName": str(node.__class__.__name__),
# "hostingMachineName": str(node.hostingMachine.hostname), # "hostingMachineName": str(node.getHostingMachine().hostname ),
# "commands": commandArray }) # "commands": commandArray })
#
# return {"status": "error", "errors":dbgCheck} #return {"status": "error", "errors":dbgCheck}
# phase three: deploy the nodes # phase three: deploy the nodes
for node in nodesToBeDeployed: for node in nodesToBeDeployed:
...@@ -218,7 +258,6 @@ class SettyController: ...@@ -218,7 +258,6 @@ class SettyController:
# phase four: cleanup generated commands # phase four: cleanup generated commands
for serviceNode in nodesToBeDeployed: for serviceNode in nodesToBeDeployed:
serviceNode.generatedCommands = None serviceNode.generatedCommands = None
serviceNode.hostingMachine = None
if errorMessages: if errorMessages:
return {'status': 'error', return {'status': 'error',
......
...@@ -3,55 +3,59 @@ import salt.config ...@@ -3,55 +3,59 @@ import salt.config
import salt.runner import salt.runner
import salt.client import salt.client
import logging
logger = logging.getLogger(__name__)
class SaltCommand: class SaltCommand:
def __init__(self): def __init__(self):
self.hostname = "" self.hostname = ""
self.command = "" self.command = ""
self.parameters = "" self.parameters = None
# For debugging purposes only # For debugging purposes only
def __str__(self):
return "Command: " + self.hostname + " - " + self.command + " - " + str(self.parameters)
def toDict(self): def toDict(self):
return { 'hostname': self.hostname, 'command': self.command, 'parameters': self.parameters } return {'hostname': self.hostname,
'command': self.command,
'parameters': self.parameters}
class SaltStackHelper: class SaltStackHelper:
def __init__(self): def __init__(self):
self.master_opts = salt.config.client_config('/etc/salt/master') self.master_opts = salt.config.client_config('/etc/salt/master')
self.salt_runner = salt.runner.RunnerClient(self.master_opts) self.salt_runner = salt.runner.RunnerClient(self.master_opts)
self.salt_localclient = salt.client.LocalClient() self.salt_localclient = salt.client.LocalClient()
def getAllMinionsGrouped(self): def getAllMinionsGrouped(self):
query_result = self.salt_runner.cmd('manage.status', []); query_result = self.salt_runner.cmd('manage.status', [])
return query_result return query_result
def getAllMinionsUngrouped(self): def getAllMinionsUngrouped(self):
query_result = self.salt_runner.cmd('manage.status', []); query_result = self.salt_runner.cmd('manage.status', [])
return query_result["up"] + query_result["down"] return query_result["up"] + query_result["down"]
def getRunningMinions(self): def getRunningMinions(self):
return self.salt_runner.cmd('manage.up', []); return self.salt_runner.cmd('manage.up', [])
def getUnavailableMinions(self): def getUnavailableMinions(self):
return self.salt_runner.cmd('manage.down', []); return self.salt_runner.cmd('manage.down', [])
def getMinionBasicHardwareInfo(self, hostname):
query_res = self.salt_localclient.cmd( hostname,'grains.items' );
if query_res:
return {
'CpuModel': query_res[hostname]['cpu_model'],
'CpuArch': query_res[hostname]['cpuarch'],
'TotalMemory': query_res[hostname]['mem_total'],
'OSDescription': query_res[hostname]['lsb_distrib_description'] }
return query_res
def checkMinionExists(self, hostname): def checkMinionExists(self, hostname):
query_res = self.salt_localclient.cmd( hostname,'network.get_hostname' ); query_res = self.salt_localclient.cmd(hostname, 'network.get_hostname')
return query_res != {} return query_res != {}
def getIpAddressOfMinion(self, hostname):
query_res = self.salt_localclient.cmd(hostname, 'network.ip_addrs')
return query_res[hostname][0]
def executeCommand(self, saltCommands): def executeCommand(self, saltCommands):
for saltCommand in saltCommands: for command in saltCommands:
self.salt_localclient.cmd(saltCommand.hostname, "state.sls",[saltCommand.command],kwarg={"pillar":saltCommand.parameters} ) if command.parameters:
self.salt_localclient.cmd(command.hostname, "state.sls",
[command.command],
kwarg={"pillar": command.parameters})
else:
self.salt_localclient.cmd(command.hostname, "state.sls",
[command.command])
...@@ -29,6 +29,15 @@ $.ajaxSetup({ ...@@ -29,6 +29,15 @@ $.ajaxSetup({
} }
}); });
showErrorDialog = function( errors ){
$("#errorListContainer").empty();
$.each( errors, function(index, message){
icon = $("<i/>").addClass("fa").addClass("fa-remove");
item = $("<li/>").addClass("list-group-item").addClass("list-group-item-danger").append(icon).append( message );
$("#errorListContainer").append( item );
});
$("#errorDialog").modal('show');
}
/* Setty implementation starts here. */ /* Setty implementation starts here. */
...@@ -101,11 +110,12 @@ jsPlumb.ready(function() { ...@@ -101,11 +110,12 @@ jsPlumb.ready(function() {
event: "addMachine", event: "addMachine",
data: JSON.stringify({ "hostname": machineHostname } ) data: JSON.stringify({ "hostname": machineHostname } )
}, function(result) { }, function(result) {
if(result.error) if(result.errors)
{ {
alert(result.error) showErrorDialog( result.errors );
return; return;
} }
addMachine( result ); addMachine( result );
undoStack.splice(stackIndexer, 0, removeElement); undoStack.splice(stackIndexer, 0, removeElement);
redoStack.splice(stackIndexer, 0, addElement); redoStack.splice(stackIndexer, 0, addElement);
...@@ -793,14 +803,14 @@ jsPlumb.ready(function() { ...@@ -793,14 +803,14 @@ jsPlumb.ready(function() {
$('body').on('click', '#deployService',function() { $('body').on('click', '#deployService',function() {
if($("#serviceStatus").text() == "Unsaved") { if($("#serviceStatus").text() == "Unsaved") {
alert("Only saved services can be deployed"); showErrorDialog( ["Only saved services can be deployed"]);
return; return;
} }
$.post("", { $.post("", {
event: "deploy", event: "deploy",
}, function(result) { }, function(result) {
if ( result.status ) if ( result.errors )
alert( result.errors ); showErrorDialog( result.errors );
else else
alert("Deploying...."); alert("Deploying....");
}); });
......
...@@ -97,6 +97,29 @@ ...@@ -97,6 +97,29 @@
</div> </div>
</div> </div>
<div class="modal fade" id="errorDialog" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal">&times;</button>
<h4 class="modal-title"><i class="fa fa-trash-o"></i> {% trans 'Error occured during operation' %}</h4>
</div>
<div class="modal-body">
<p>{% trans 'The following errors occured' %}:</p>
<ul id="errorListContainer" class="list-group">
</ul>
</div>
<div class="modal-footer">
<div class="row">
<div class="col-xs-2">
<button class="btn btn-primary btn-md" type="button" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Adding element dialog --> <!-- Adding element dialog -->
<div class="modal fade" id="addElementDialog" role="dialog"> <div class="modal fade" id="addElementDialog" role="dialog">
......
...@@ -18,14 +18,13 @@ ...@@ -18,14 +18,13 @@
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Q
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from django.views.generic import TemplateView, DeleteView from django.views.generic import TemplateView, DeleteView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from saltstackhelper import * from controller import SettyController
from controller import * from models import Service, ElementTemplate
from dashboard.views.util import FilterMixin from dashboard.views.util import FilterMixin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import json import json
...@@ -86,7 +85,7 @@ class DetailView(LoginRequiredMixin, TemplateView): ...@@ -86,7 +85,7 @@ class DetailView(LoginRequiredMixin, TemplateView):
'serviceNodes'], data['machines'], data['elementConnections']) 'serviceNodes'], data['machines'], data['elementConnections'])
elif eventName == "getMachineAvailableList": elif eventName == "getMachineAvailableList":
result = SettyController.getMachineAvailableList( result = SettyController.getMachineAvailableList(
serviceId, data["usedHostnames"], self.request.user ) serviceId, data["usedHostnames"], self.request.user)
elif eventName == "addServiceNode": elif eventName == "addServiceNode":
result = SettyController.addServiceNode( result = SettyController.addServiceNode(
data["elementTemplateId"]) data["elementTemplateId"])
...@@ -151,7 +150,7 @@ class CreateView(LoginRequiredMixin, TemplateView): ...@@ -151,7 +150,7 @@ class CreateView(LoginRequiredMixin, TemplateView):
service_name = "Noname" service_name = "Noname"
try: try:
serviceNameAvailable = Service.objects.get(name=service_name) Service.objects.get(name=service_name)
raise PermissionDenied raise PermissionDenied
except Service.DoesNotExist: except Service.DoesNotExist:
pass pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment