Commit 3dc93ded by Sulyok Gabor

Changes and fixes for deployment

parent 97c4dbde

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',
......
...@@ -16,21 +16,19 @@ ...@@ -16,21 +16,19 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.db import models from django.db import models
from django.db.models import Model, Q from django.db.models import Q
from django.db.models.signals import post_init
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User from django.contrib.auth.models import User
from taggit.managers import TaggableManager
from django.utils.translation import ugettext_lazy as _
from storage import OverwriteStorage from storage import OverwriteStorage
import os import os
import yaml import yaml
from saltstackhelper import SaltCommand from saltstackhelper import SaltCommand, SaltStackHelper
SALTSTACK_PILLAR_FOLDER = "/srv/pillar" SALTSTACK_PILLAR_FOLDER = "/srv/pillar"
# Replacer method for configuration generation # Replacer method for configuration generation
salthelper = SaltStackHelper()
def replaceParameter(pillar, parameterToReplace, newValue): def replaceParameter(pillar, parameterToReplace, newValue):
pillarEdited = pillar.replace(parameterToReplace, str(newValue)) pillarEdited = pillar.replace(parameterToReplace, str(newValue))
...@@ -49,35 +47,44 @@ class Service(models.Model): ...@@ -49,35 +47,44 @@ class Service(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
''' Defines caterogies for ElementTemplates. '''
class ElementCategory(models.Model): class ElementCategory(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50) # Name of the category
# If the current category is a subcategory, store the ancestor
parent_category = models.ForeignKey( parent_category = models.ForeignKey(
'self', on_delete=models.CASCADE, null=True, blank=True) 'self', on_delete=models.CASCADE, null=True, blank=True)
def __unicode__(self): def __unicode__(self):
return self.name return self.name
# Defines a model for ElementTemplates which are used to create real elements on the GUI '''
# Hold Defines a model for ElementTemplates which are used to create real elements ServiceNodes
'''
class ElementTemplate(models.Model): class ElementTemplate(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
logo = models.FileField(upload_to='setty/', storage=OverwriteStorage()) logo = models.FileField(
description = models.TextField() upload_to='setty/', storage=OverwriteStorage()) # Logo for the
description = models.TextField() # Description of a ServiceNode
# List of wich ServiceNodes are compatible
compatibles = models.ManyToManyField('self', blank=True) compatibles = models.ManyToManyField('self', blank=True)
category = models.ForeignKey( category = models.ForeignKey(
ElementCategory, on_delete=models.CASCADE, null=True) ElementCategory, on_delete=models.CASCADE, null=True) # Which category
# The name of the Model which the template represents
prototype = models.TextField(default="<SYNTAX ERROR>") prototype = models.TextField(default="<SYNTAX ERROR>")
def __unicode__(self): def __unicode__(self):
return self.name return self.name
# http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982 '''
# Super base class to prevent messing the code in the controller http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982
# it saves the type info to DB, and when the objects are queried, the cast method returns the real class Base class to prevent messing with the code in the controller
# not the base it saves the type info to DB, and when the objects are queried, the cast method returns the real class
not the base
'''
class InheritanceCastModel(models.Model): class InheritanceCastModel(models.Model):
...@@ -97,6 +104,11 @@ class InheritanceCastModel(models.Model): ...@@ -97,6 +104,11 @@ class InheritanceCastModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
'''
Element stores the base information for all types of nodes shown in the designer.
It stores the id, position and number of anchors
'''
class Element(InheritanceCastModel): class Element(InheritanceCastModel):
display_id = models.TextField() display_id = models.TextField()
...@@ -104,20 +116,24 @@ class Element(InheritanceCastModel): ...@@ -104,20 +116,24 @@ class Element(InheritanceCastModel):
position_top = models.FloatField() position_top = models.FloatField()
anchor_number = models.PositiveSmallIntegerField() anchor_number = models.PositiveSmallIntegerField()
# Get the display related data in a dictionary
def getDisplayData(self): def getDisplayData(self):
return {'displayId': self.display_id, return {'displayId': self.display_id,
'positionLeft': self.position_left, 'positionLeft': self.position_left,
'positionTop': self.position_top, 'positionTop': self.position_top,
'anchorNumber': self.anchor_number} 'anchorNumber': self.anchor_number}
# Set the display related data from a dictionary
def setDisplayData(self, data): def setDisplayData(self, data):
self.display_id = data["displayId"] self.display_id = data["displayId"]
self.position_left = data["positionLeft"] self.position_left = data["positionLeft"]
self.position_top = data["positionTop"] self.position_top = data["positionTop"]
self.anchor_number = data["anchorNumber"] self.anchor_number = data["anchorNumber"]
# ElementConnection represents connection between Elements. Has a source '''
# and a target endpoint. ElementConnection represents connection between Elements. Has a source
and a target endpoint.
'''
class ElementConnection(models.Model): class ElementConnection(models.Model):
...@@ -139,30 +155,38 @@ class ElementConnection(models.Model): ...@@ -139,30 +155,38 @@ class ElementConnection(models.Model):
return {'targetEndpoint': self.target_endpoint, return {'targetEndpoint': self.target_endpoint,
'sourceEndpoint': self.source_endpoint} 'sourceEndpoint': self.source_endpoint}
# Represents an CIRCLE VM Instance which is known by Salt-Master and used '''
# in Setty configuration Represents an CIRCLE VM Instance which is known by Salt-Master and used
in Setty configuration
'''
class Machine(Element): class Machine(Element):
MACHINE_STATUS_CHOICES = ( MACHINE_STATUS_CHOICES = (
(1, 'Running'), (1, 'Running'),
(2, 'Unreachable')) (2, 'Unreachable'))
# Which service contains the given Machine instnace
service = models.ForeignKey( service = models.ForeignKey(
Service, on_delete=models.CASCADE, related_name="service_id") Service, on_delete=models.CASCADE, related_name="service_id")
hostname = models.TextField(null=False) # also serves as salt-minion id hostname = models.TextField(null=False) # also serves as salt-minion id
# User given alias for the machine
alias = models.CharField(max_length=50) alias = models.CharField(max_length=50)
# User's description for the machine
description = models.TextField(default="") description = models.TextField(default="")
status = models.CharField(choices=MACHINE_STATUS_CHOICES, max_length=1) status = models.CharField(choices=MACHINE_STATUS_CHOICES, max_length=1)
def __unicode__(self): def __unicode__(self):
return "%s" % self.hostname return "%s" % self.hostname
# Get the name and the type of the editable fields
@staticmethod @staticmethod
def getInformation(): def getInformation():
return {'hostname': Machine._meta.get_field('hostname').get_internal_type(), return {'hostname': Machine._meta.get_field('hostname').get_internal_type(),
'alias': Machine._meta.get_field('alias').get_internal_type(), 'alias': Machine._meta.get_field('alias').get_internal_type(),
'description': Machine._meta.get_field('description').get_internal_type()} 'description': Machine._meta.get_field('description').get_internal_type()}
# Get the all the Machine related data in a dictionary
def getDataDictionary(self): def getDataDictionary(self):
element_data = self.getDisplayData() element_data = self.getDisplayData()
...@@ -172,6 +196,7 @@ class Machine(Element): ...@@ -172,6 +196,7 @@ class Machine(Element):
element_data.update(self_data) element_data.update(self_data)
return element_data return element_data
# Fill the fields from the given dictioary
def fromDataDictionary(self, data): def fromDataDictionary(self, data):
self.setDisplayData(data) self.setDisplayData(data)
...@@ -179,27 +204,34 @@ class Machine(Element): ...@@ -179,27 +204,34 @@ class Machine(Element):
self.alias = data["alias"] self.alias = data["alias"]
self.description = data["description"] self.description = data["description"]
# Create a new instance of Machine
@staticmethod @staticmethod
def clone(): def clone():
return Machine() return Machine()
class ServiceNode(Element): class ServiceNode(Element):
# The Service which the ServiceNode belongs to
service = models.ForeignKey( service = models.ForeignKey(
Service, on_delete=models.CASCADE, default=None) Service, on_delete=models.CASCADE, default=None)
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
# TODO: Remove this
config_file = models.FileField( config_file = models.FileField(
default=None, upload_to='setty/node_configs/', storage=OverwriteStorage()) default=None, upload_to='setty/node_configs/', storage=OverwriteStorage())
# User's description for the ServiceNode
description = models.TextField(default="") description = models.TextField(default="")
# Override the default init function to create generatedCommands
# instance member to store the SaltCommands for deployment
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ServiceNode, self).__init__(*args, **kwargs) super(ServiceNode, self).__init__(*args, **kwargs)
self.hostingMachine = None
self.generatedCommands = [] self.generatedCommands = []
def __unicode__(self): def __unicode__(self):
return "%s" % self.name return "%s" % self.name
# Get the saved data from the node
def getDataDictionary(self): def getDataDictionary(self):
element_data = self.getDisplayData() element_data = self.getDisplayData()
...@@ -209,11 +241,13 @@ class ServiceNode(Element): ...@@ -209,11 +241,13 @@ class ServiceNode(Element):
element_data.update(self_data) element_data.update(self_data)
return element_data return element_data
# Fill the members of the class from dictionary
def fromDataDictionary(self, data): def fromDataDictionary(self, data):
self.setDisplayData(data) self.setDisplayData(data)
self.name = data['name'] self.name = data['name']
self.description = data['description'] self.description = data['description']
# Get fieldnames and types in a dictionary
@staticmethod @staticmethod
def getInformation(): def getInformation():
return {'name': ServiceNode._meta.get_field('name').get_internal_type(), return {'name': ServiceNode._meta.get_field('name').get_internal_type(),
...@@ -223,10 +257,17 @@ class ServiceNode(Element): ...@@ -223,10 +257,17 @@ class ServiceNode(Element):
def clone(): def clone():
raise PermissionDenied raise PermissionDenied
# functions for deployement
# Check that all the required fields are set, and
# has the connections for the other services or machines
# which are needed
def checkDependenciesAndAttributes(self): def checkDependenciesAndAttributes(self):
return [] return []
# functions for deployement # Checks whether the current Node has a connection to
# an other node (Machine or ServiceNode derivative).
# The other object's type is the ObjOther parameter
# Returns the queryed node if it exists, or None
def checkDependecy(self, ObjOther): def checkDependecy(self, ObjOther):
elementConnections = ElementConnection.objects.filter( elementConnections = ElementConnection.objects.filter(
Q(target=self) | Q(source=self)) Q(target=self) | Q(source=self))
...@@ -240,46 +281,50 @@ class ServiceNode(Element): ...@@ -240,46 +281,50 @@ class ServiceNode(Element):
return connection.target.cast() return connection.target.cast()
return None return None
# Compare functions for sorting the ServiceNodes by DeploymentPriority
def __cmp__(self, other): def __cmp__(self, other):
if not isinstance( other, ServiceNode ): if not isinstance(other, ServiceNode):
raise PermissionDenied raise PermissionDenied
return self.getDeploymentPriority(self).__cmp__(other.getDeploymentPriority(other)) return self.getDeploymentPriority(self).__cmp__(other.getDeploymentPriority(other))
# Returns the hosting Machine for the ServiceNode. It must be overriden
# for nodes where they depend on an other service, and the hosting machine
# is the other service's hosting machine
def getHostingMachine(self): def getHostingMachine(self):
if self.hostingMachine:
return self.hostingMachine
elementConnections = ElementConnection.objects.filter( elementConnections = ElementConnection.objects.filter(
Q(target=self) | Q(source=self)) Q(target=self) | Q(source=self))
for connection in elementConnections: for connection in elementConnections:
if isinstance(connection.target.cast(), Machine): if isinstance(connection.target.cast(), Machine):
self.hostingMachine = connection.target.cast() return connection.target.cast()
return self.hostingMachine
if isinstance(connection.source.cast(), Machine): if isinstance(connection.source.cast(), Machine):
self.hostingMachine = connection.source.cast() return connection.source.cast()
return self.hostingMachine
raise PermissionDenied raise PermissionDenied
# Return the deployment priority. The nodes are sorted by this attribute
# and deployed in that order
def getDeploymentPriority(self): def getDeploymentPriority(self):
return 0 return 0
# Generate the salt commands which are needed to deploy the service
def generateSaltCommands(self): def generateSaltCommands(self):
raise PermissionDenied raise PermissionDenied
# Replace the parameters in the given pillar with the stored values
def replacePillarParameters(self, pillar): def replacePillarParameters(self, pillar):
raise PermissionDenied raise PermissionDenied
class WordpressNode(ServiceNode): class WordpressNode(ServiceNode):
# DB related fields # DB related fields
databaseName = models.TextField(default="") databaseName = models.TextField(default="")
databaseHost = models.TextField(default="") databaseHost = models.TextField(default="") # todo: remove this
databaseUser = models.TextField(default="") databaseUser = models.TextField(default="")
databasePass = models.TextField(default="") databasePass = models.TextField(default="")
# admin user # Admin user for the
adminUsername = models.TextField(default="") adminUsername = models.TextField(default="")
adminPassword = models.TextField(default="") adminPassword = models.TextField(default="")
adminEmail = models.TextField(default="") adminEmail = models.TextField(default="")
...@@ -322,7 +367,9 @@ class WordpressNode(ServiceNode): ...@@ -322,7 +367,9 @@ class WordpressNode(ServiceNode):
@staticmethod @staticmethod
def getInformation(): def getInformation():
superInformation = ServiceNode.getInformation() superInformation = ServiceNode.getInformation()
ownInformation = {'database-name': WordpressNode._meta.get_field('databaseName').get_internal_type(), ownInformation = {'database-name':
WordpressNode._meta.get_field(
'databaseName').get_internal_type(),
'database-host': WordpressNode._meta.get_field('databaseHost').get_internal_type(), 'database-host': WordpressNode._meta.get_field('databaseHost').get_internal_type(),
'database-user': WordpressNode._meta.get_field('databaseUser').get_internal_type(), 'database-user': WordpressNode._meta.get_field('databaseUser').get_internal_type(),
'database-pass': WordpressNode._meta.get_field('databasePass').get_internal_type(), 'database-pass': WordpressNode._meta.get_field('databasePass').get_internal_type(),
...@@ -339,45 +386,41 @@ class WordpressNode(ServiceNode): ...@@ -339,45 +386,41 @@ class WordpressNode(ServiceNode):
errorMessages = ServiceNode.checkDependenciesAndAttributes(self) errorMessages = ServiceNode.checkDependenciesAndAttributes(self)
if not self.databaseHost: if not self.databaseHost:
errorMessage.append("DATABASENAME_NOT_SET") errorMessages.append("DATABASENAME_NOT_SET")
if not self.databaseHost: if not self.databaseHost:
errorMessage.append("DATABASEHOST_NOT_SET") errorMessages.append("DATABASEHOST_NOT_SET")
if not self.databaseUser: if not self.databaseUser:
errorMessage.append("DATABASEUSER_NOT_SET") errorMessages.append("DATABASEUSER_NOT_SET")
if not self.databasePass: if not self.databasePass:
errorMessage.append("DATABASEPASS_NOT_SET") errorMessages.append("DATABASEPASS_NOT_SET")
if not self.adminUsername: if not self.adminUsername:
errorMessage.append("ADMINUSERNAME_NOT_SET") errorMessages.append("ADMINUSERNAME_NOT_SET")
if not self.adminPassword: if not self.adminPassword:
errorMessage.append("ADMINPASSWORD_NOT_SET") errorMessages.append("ADMINPASSWORD_NOT_SET")
if not self.adminEmail: if not self.adminEmail:
errorMessage.append("ADMINEMAIL_NOT_SET") errorMessages.append("ADMINEMAIL_NOT_SET")
if not self.siteTitle: if not self.siteTitle:
errorMessage.append("SITETITLE_NOT_SET") errorMessages.append("SITETITLE_NOT_SET")
if not self.siteUrl: if not self.siteUrl:
errorMessage.append("SITEURL_NOT_SET") errorMessages.append("SITEURL_NOT_SET")
if not self.checkDependecy(MySQLNode): if not self.checkDependecy(MySQLNode):
errorMessage.append("NODE_NOT_CONNECTED") errorMessages.append("NODE_NOT_CONNECTED")
if not self.checkDependecy(WebServerNode): if not self.checkDependecy(WebServerNode):
errorMessage.append("NODE_NOT_CONNECTED") errorMessages.append("NODE_NOT_CONNECTED")
return errorMessages return errorMessages
def getHostingMachine(self): def getHostingMachine(self):
if self.hostingMachine:
return hostingMachine
apacheNode = self.checkDependecy(ApacheNode) apacheNode = self.checkDependecy(ApacheNode)
if not apacheNode: if not apacheNode:
raise PermissionDenied raise PermissionDenied
self.hostingMachine = apacheNode.getHostingMachine() hostingMachine = apacheNode.getHostingMachine()
if not self.hostingMachine: if not hostingMachine:
raise PermissionDenied raise PermissionDenied
return self.hostingMachine return hostingMachine
@staticmethod @staticmethod
def getDeploymentPriority(self): def getDeploymentPriority(self):
...@@ -385,21 +428,21 @@ class WordpressNode(ServiceNode): ...@@ -385,21 +428,21 @@ class WordpressNode(ServiceNode):
def replacePillarParameters(self, pillar): def replacePillarParameters(self, pillar):
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%DATABASE_NAME%%', self.databaseName) pillar, r'DATABASE_NAME', self.databaseName)
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%DATABASE_HOST%%', self.databaseHost) pillar, r'DATABASE_HOST', self.databaseHost)
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%DATABASE_USER%%', self.databaseUser) pillar, r'DATABASE_USER', self.databaseUser)
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%DATABASE_PASS%%', self.databasePass) pillar, r'DATABASE_PASS', self.databasePass)
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%ADMIN_USERNAME%%', self.adminUsername) pillar, r'ADMIN_USERNAME', self.adminUsername)
pillar = replaceParameter( pillar = replaceParameter(
pillar, r'%%ADMIN_PASSWORD%%', self.adminPassword) pillar, r'ADMIN_PASSWORD', self.adminPassword)
pillar = replaceParameter(pillar, r'%%ADMIN_EMAIL%%', self.adminEmail) pillar = replaceParameter(pillar, r'ADMIN_EMAIL', self.adminEmail)
pillar = replaceParameter(pillar, r'%%SITE_TITLE%%', self.siteTitle) pillar = replaceParameter(pillar, r'SITE_TITLE', self.siteTitle)
pillar = replaceParameter(pillar, r'%%SITE_URL%%', self.siteUrl) pillar = replaceParameter(pillar, r'SITE_URL', self.siteUrl)
return pillar return pillar
...@@ -412,33 +455,50 @@ class WordpressNode(ServiceNode): ...@@ -412,33 +455,50 @@ class WordpressNode(ServiceNode):
if not apacheNode: if not apacheNode:
raise PermissionDenied raise PermissionDenied
self.hostingMachine = apacheNode.getHostingMachine() installPhpCommand = apacheNode.makeInstallPhpCommand()
restartApacheCommand = apacheNode.makeRestartCommand()
createMySQLUserCommand = mysqlNode.makeCreateDatabaseCommand( createMySQLDatabaseCommand = mysqlNode.makeCreateDatabaseCommand(
self.databaseName) self.databaseName)
createMySQLDatabaseCommand = mysqlNode.makeCreateUserCommand( createMySQLUserCommand = mysqlNode.makeCreateUserCommand(
self.databaseUser, self.databasePass, self.databaseName) self.databaseUser, self.databasePass, self.databaseName)
pillarFilePath = os.path.join( installWordpressCommand = SaltCommand()
SALTSTACK_PILLAR_FOLDER, "wordpress.sls") installWordpressCommand.hostname = self.getHostingMachine().hostname
with open(pillarFilePath, 'r') as pillarFile: installWordpressCommand.command = "wordpress"
pillar = str(yaml.load(pillarFile)) installWordpressCommand.parameters = {'wordpress':
pillar = self.replacePillarParameters(pillar) {'cli':
{'source':
saltCommand = SaltCommand() 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar',
saltCommand.hostname = self.hostingMachine.hostname 'hash':
saltCommand.command = "wordpress" 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512'},
saltCommand.parameters = [eval(pillar)] 'lookup': {'docroot': '/var/www/html'},
'sites':
self.generatedCommands = [] {'mysitename.com':
{'username': self.adminUsername,
'password': self.adminPassword,
'database': self.databaseName,
'dbhost': salthelper.getIpAddressOfMinion( mysqlNode.getHostingMachine().hostname ),
'dbuser': self.databaseUser,
'dbpass': self.databasePass,
'url': self.siteUrl,
'title': self.siteTitle,
'email': self.adminEmail}}}}
installMySqlClientCommand = SaltCommand()
installMySqlClientCommand.hostname = self.getHostingMachine().hostname
installMySqlClientCommand.command = "mysql.client"
self.generatedCommands.append(installPhpCommand)
self.generatedCommands.append(restartApacheCommand)
self.generatedCommands.append(installMySqlClientCommand)
self.generatedCommands.append(createMySQLDatabaseCommand) self.generatedCommands.append(createMySQLDatabaseCommand)
self.generatedCommands.append(createMySQLUserCommand) self.generatedCommands.append(createMySQLUserCommand)
self.generatedCommands.append(saltCommand) self.generatedCommands.append(installWordpressCommand)
class WebServerNode(ServiceNode): class WebServerNode(ServiceNode):
useSSL = models.BooleanField(default=False) useSSL = models.BooleanField(default=False) # todo: remove this
listeningPort = models.PositiveIntegerField() listeningPort = models.PositiveIntegerField() # todo: remove this
def getDataDictionary(self): def getDataDictionary(self):
element_data = ServiceNode.getDataDictionary(self) element_data = ServiceNode.getDataDictionary(self)
...@@ -478,9 +538,9 @@ class WebServerNode(ServiceNode): ...@@ -478,9 +538,9 @@ class WebServerNode(ServiceNode):
return 10 return 10
def replacePillarParameters(self, pillar): def replacePillarParameters(self, pillar):
pillar = replaceParameter(pillar, r"%%USE_SSL%%", self.useSSL) pillar = replaceParameter(pillar, r"USE_SSL", self.useSSL)
pillar = replaceParameter(pillar, pillar = replaceParameter(pillar,
r"%%LISTENING_PORT%%", self.listeningPort) r"LISTENING_PORT", self.listeningPort)
return pillar return pillar
...@@ -491,20 +551,27 @@ class ApacheNode(WebServerNode): ...@@ -491,20 +551,27 @@ class ApacheNode(WebServerNode):
return ApacheNode() return ApacheNode()
def generateSaltCommands(self): def generateSaltCommands(self):
pillarFilePath = os.path.join(
SALTSTACK_PILLAR_FOLDER, "apache.sls")
with open(pillarFilePath, 'r') as pillarFile:
pillar = str(yaml.load(pillarFile))
pillar = WebServerNode.replacePillarParameters(self, pillar)
saltCommand = SaltCommand() saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "apache" saltCommand.command = "apache"
saltCommand.parameters = [eval(pillar)]
self.generatedCommands = [] self.generatedCommands = []
self.generatedCommands.append(saltCommand) self.generatedCommands.append(saltCommand)
def makeInstallPhpCommand(self):
saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "php-simple"
return saltCommand
def makeRestartCommand(self):
saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "apache.restart"
return saltCommand
class NginxNode(WebServerNode): class NginxNode(WebServerNode):
worker_connections = models.PositiveIntegerField() worker_connections = models.PositiveIntegerField()
...@@ -545,7 +612,7 @@ class NginxNode(WebServerNode): ...@@ -545,7 +612,7 @@ class NginxNode(WebServerNode):
pillar = str(yaml.load(pillarFile)) pillar = str(yaml.load(pillarFile))
pillar = WebServerNode.replacePillarParameters(self, pillar) pillar = WebServerNode.replacePillarParameters(self, pillar)
pillar = replaceParameter(pillar, pillar = replaceParameter(pillar,
r"%%WORKER_CONNECTIONS%%", self.worker_connections) r"WORKER_CONNECTIONS", self.worker_connections)
saltCommand = SaltCommand() saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname saltCommand.hostname = self.getHostingMachine().hostname
...@@ -557,9 +624,9 @@ class NginxNode(WebServerNode): ...@@ -557,9 +624,9 @@ class NginxNode(WebServerNode):
class DatabaseNode(ServiceNode): class DatabaseNode(ServiceNode):
adminUserName = models.CharField(max_length=50) adminUserName = models.CharField(max_length=50) # todo: remove this
adminPassword = models.CharField(max_length=50) adminPassword = models.CharField(max_length=50)
listeningPort = models.PositiveIntegerField() listeningPort = models.PositiveIntegerField() # todo: remove this
def getDataDictionary(self): def getDataDictionary(self):
element_data = ServiceNode.getDataDictionary(self) element_data = ServiceNode.getDataDictionary(self)
...@@ -605,11 +672,11 @@ class DatabaseNode(ServiceNode): ...@@ -605,11 +672,11 @@ class DatabaseNode(ServiceNode):
def replacePillarParameters(self, pillar): def replacePillarParameters(self, pillar):
pillar = replaceParameter(pillar, pillar = replaceParameter(pillar,
r"%%ADMIN_USERNAME%%", self.adminUserName) r"ADMIN_USERNAME", self.adminUserName)
pillar = replaceParameter(pillar, pillar = replaceParameter(pillar,
r"%%ADMIN_PASSWORD%%", self.adminUserName) r"ADMIN_PASSWORD", self.adminUserName)
pillar = replaceParameter(pillar, pillar = replaceParameter(pillar,
r'%%LISTENING_PORT%%', self.listeningPort) r'LISTENING_PORT', self.listeningPort)
return pillar return pillar
...@@ -641,32 +708,47 @@ class MySQLNode(DatabaseNode): ...@@ -641,32 +708,47 @@ class MySQLNode(DatabaseNode):
def clone(): def clone():
return MySQLNode() return MySQLNode()
# Generate SaltCommand for database creation on the current MySQL instance
def makeCreateDatabaseCommand(self, databaseName): def makeCreateDatabaseCommand(self, databaseName):
saltCommand = SaltCommand() saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "mysql.database" saltCommand.command = "mysql.database"
saltCommand.parameters = [databaseName] saltCommand.parameters = {'mysql': {
'server': {'root_password': self.adminPassword}, 'database': [databaseName]}}
return saltCommand return saltCommand
# Generate SaltCommand for user creation on the current MySQL instance
def makeCreateUserCommand(self, databaseUser, databasePass, availableDatabases): def makeCreateUserCommand(self, databaseUser, databasePass, availableDatabases):
saltCommand = SaltCommand() saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "mysql.user" saltCommand.command = "mysql.user"
saltCommand.parameters = {databaseUser: {'password': databasePass, 'host': 'localhost', 'databases': [ databaseGrants = [{'database': '*', 'grants': ['all privileges']}]
{'database': availableDatabases, 'grants': ['all privileges']}]}} if isinstance(availableDatabases, list):
for dbAccess in availableDatabases:
databaseGrants.append(
{'database': dbAccess, 'grants': ['all privileges']})
else:
databaseGrants.append(
{'database': availableDatabases, 'grants': ['all privileges']})
saltCommand.parameters = {'mysql': {
'server': {
'root_password': self.adminPassword
},
'user': {
databaseUser:
{'password': databasePass,
'host': '%',
'databases': databaseGrants
}}}}
return saltCommand return saltCommand
def generateSaltCommands(self): def generateSaltCommands(self):
pillarFilePath = os.path.join(
SALTSTACK_PILLAR_FOLDER, "mysql.sls")
with open(pillarFilePath, 'r') as pillarFile:
pillar = str(yaml.load(pillarFile))
pillar = DatabaseNode.replacePillarParameters(self, pillar)
saltCommand = SaltCommand() saltCommand = SaltCommand()
saltCommand.hostname = self.getHostingMachine().hostname saltCommand.hostname = self.getHostingMachine().hostname
saltCommand.command = "mysql.server" saltCommand.command = "mysql.server"
saltCommand.parameters = [eval(pillar)] saltCommand.parameters = {
'mysql': {'server': {'root_password': self.adminPassword, 'mysqld':{'bind-address' : '0.0.0.0'}}}}
self.generatedCommands = [] self.generatedCommands = []
self.generatedCommands.append(saltCommand) self.generatedCommands.append(saltCommand)
...@@ -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