Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gyuricska Milán
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
3dc93ded
authored
Nov 06, 2016
by
Sulyok Gabor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changes and fixes for deployment
parent
97c4dbde
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
338 additions
and
181 deletions
+338
-181
circle/media/setty/apache.jpg
+0
-0
circle/media/setty/mysql.jpg
+0
-0
circle/media/setty/wordpress.jpg
+0
-0
circle/setty/controller.py
+70
-31
circle/setty/models.py
+198
-116
circle/setty/saltstackhelper.py
+27
-23
circle/setty/static/setty/setty.js
+15
-5
circle/setty/templates/setty/index.html
+23
-0
circle/setty/views.py
+5
-6
No files found.
circle/media/setty/apache.jpg
View file @
3dc93ded
15.9 KB
|
W:
|
H:
15.9 KB
|
W:
|
H:
2-up
Swipe
Onion skin
circle/media/setty/mysql.jpg
View file @
3dc93ded
9.07 KB
|
W:
|
H:
9.07 KB
|
W:
|
H:
2-up
Swipe
Onion skin
circle/media/setty/wordpress.jpg
View file @
3dc93ded
19.9 KB
|
W:
|
H:
19.9 KB
|
W:
|
H:
2-up
Swipe
Onion skin
circle/setty/controller.py
View file @
3dc93ded
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
:
r
aise
PermissionDenied
# TODO: something more meaningful
r
eturn
{
'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
]
}
availableInstances
=
list
(
set
(
userMachines
)
-
usedHostnamesByUser
)
return
{
'machinedata'
:
[
machineName
for
machineName
in
availableInstances
if
machineName
in
saltMinions
]}
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,8 +223,9 @@ class SettyController:
...
@@ -186,8 +223,9 @@ 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
:
...
@@ -215,10 +255,9 @@ class SettyController:
...
@@ -215,10 +255,9 @@ class SettyController:
if
errorMessages
:
if
errorMessages
:
errorMessages
.
append
(
deployErrorMessages
)
errorMessages
.
append
(
deployErrorMessages
)
# 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'
,
...
...
circle/setty/models.py
View file @
3dc93ded
...
@@ -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"
)
errorMessage
s
.
append
(
"DATABASENAME_NOT_SET"
)
if
not
self
.
databaseHost
:
if
not
self
.
databaseHost
:
errorMessage
.
append
(
"DATABASEHOST_NOT_SET"
)
errorMessage
s
.
append
(
"DATABASEHOST_NOT_SET"
)
if
not
self
.
databaseUser
:
if
not
self
.
databaseUser
:
errorMessage
.
append
(
"DATABASEUSER_NOT_SET"
)
errorMessage
s
.
append
(
"DATABASEUSER_NOT_SET"
)
if
not
self
.
databasePass
:
if
not
self
.
databasePass
:
errorMessage
.
append
(
"DATABASEPASS_NOT_SET"
)
errorMessage
s
.
append
(
"DATABASEPASS_NOT_SET"
)
if
not
self
.
adminUsername
:
if
not
self
.
adminUsername
:
errorMessage
.
append
(
"ADMINUSERNAME_NOT_SET"
)
errorMessage
s
.
append
(
"ADMINUSERNAME_NOT_SET"
)
if
not
self
.
adminPassword
:
if
not
self
.
adminPassword
:
errorMessage
.
append
(
"ADMINPASSWORD_NOT_SET"
)
errorMessage
s
.
append
(
"ADMINPASSWORD_NOT_SET"
)
if
not
self
.
adminEmail
:
if
not
self
.
adminEmail
:
errorMessage
.
append
(
"ADMINEMAIL_NOT_SET"
)
errorMessage
s
.
append
(
"ADMINEMAIL_NOT_SET"
)
if
not
self
.
siteTitle
:
if
not
self
.
siteTitle
:
errorMessage
.
append
(
"SITETITLE_NOT_SET"
)
errorMessage
s
.
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"
)
errorMessage
s
.
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,60 +428,77 @@ class WordpressNode(ServiceNode):
...
@@ -385,60 +428,77 @@ 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
def
generateSaltCommands
(
self
):
def
generateSaltCommands
(
self
):
mysqlNode
=
self
.
checkDependecy
(
MySQLNode
)
mysqlNode
=
self
.
checkDependecy
(
MySQLNode
)
apacheNode
=
self
.
checkDependecy
(
ApacheNode
)
apacheNode
=
self
.
checkDependecy
(
ApacheNode
)
if
not
mysqlNode
:
if
not
mysqlNode
:
raise
PermissionDenied
raise
PermissionDenied
if
not
apacheNode
:
if
not
apacheNode
:
raise
PermissionDenied
raise
PermissionDenied
self
.
hostingMachine
=
apacheNode
.
getHostingMachine
()
installPhpCommand
=
apacheNode
.
makeInstallPhpCommand
()
restartApacheCommand
=
apacheNode
.
makeRestartCommand
()
createMySQL
User
Command
=
mysqlNode
.
makeCreateDatabaseCommand
(
createMySQL
Database
Command
=
mysqlNode
.
makeCreateDatabaseCommand
(
self
.
databaseName
)
self
.
databaseName
)
createMySQL
Database
Command
=
mysqlNode
.
makeCreateUserCommand
(
createMySQL
User
Command
=
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'
:
self
.
generatedCommands
.
append
(
createMySQLDatabaseCommand
)
{
'username'
:
self
.
adminUsername
,
self
.
generatedCommands
.
append
(
createMySQLUserCommand
)
'password'
:
self
.
adminPassword
,
self
.
generatedCommands
.
append
(
saltCommand
)
'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
(
createMySQLUserCommand
)
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,19 +551,26 @@ class ApacheNode(WebServerNode):
...
@@ -491,19 +551,26 @@ class ApacheNode(WebServerNode):
return
ApacheNode
()
return
ApacheNode
()
def
generateSaltCommands
(
self
):
def
generateSaltCommands
(
self
):
pillarFilePath
=
os
.
path
.
join
(
saltCommand
=
SaltCommand
()
SALTSTACK_PILLAR_FOLDER
,
"apache.sls"
)
saltCommand
.
hostname
=
self
.
getHostingMachine
()
.
hostname
with
open
(
pillarFilePath
,
'r'
)
as
pillarFile
:
saltCommand
.
command
=
"apache"
pillar
=
str
(
yaml
.
load
(
pillarFile
))
pillar
=
WebServerNode
.
replacePillarParameters
(
self
,
pillar
)
saltCommand
=
SaltCommand
()
self
.
generatedCommands
=
[]
saltCommand
.
hostname
=
self
.
getHostingMachine
()
.
hostname
self
.
generatedCommands
.
append
(
saltCommand
)
saltCommand
.
command
=
"apache"
saltCommand
.
parameters
=
[
eval
(
pillar
)]
self
.
generatedCommands
=
[]
def
makeInstallPhpCommand
(
self
):
self
.
generatedCommands
.
append
(
saltCommand
)
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
):
...
@@ -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
(
saltCommand
=
SaltCommand
()
SALTSTACK_PILLAR_FOLDER
,
"mysql.sls"
)
saltCommand
.
hostname
=
self
.
getHostingMachine
()
.
hostname
with
open
(
pillarFilePath
,
'r'
)
as
pillarFile
:
saltCommand
.
command
=
"mysql.server"
pillar
=
str
(
yaml
.
load
(
pillarFile
))
saltCommand
.
parameters
=
{
pillar
=
DatabaseNode
.
replacePillarParameters
(
self
,
pillar
)
'mysql'
:
{
'server'
:
{
'root_password'
:
self
.
adminPassword
,
'mysqld'
:{
'bind-address'
:
'0.0.0.0'
}}}}
saltCommand
=
SaltCommand
()
saltCommand
.
hostname
=
self
.
getHostingMachine
()
.
hostname
saltCommand
.
command
=
"mysql.server"
saltCommand
.
parameters
=
[
eval
(
pillar
)]
self
.
generatedCommands
=
[]
self
.
generatedCommands
=
[]
self
.
generatedCommands
.
append
(
saltCommand
)
self
.
generatedCommands
.
append
(
saltCommand
)
circle/setty/saltstackhelper.py
View file @
3dc93ded
...
@@ -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
])
circle/setty/static/setty/setty.js
View file @
3dc93ded
...
@@ -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
.
error
s
)
{
{
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
.
statu
s
)
if
(
result
.
error
s
)
alert
(
result
.
errors
);
showErrorDialog
(
result
.
errors
);
else
else
alert
(
"Deploying...."
);
alert
(
"Deploying...."
);
});
});
...
...
circle/setty/templates/setty/index.html
View file @
3dc93ded
...
@@ -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"
>
×
</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"
>
...
...
circle/setty/views.py
View file @
3dc93ded
...
@@ -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"
])
...
@@ -103,7 +102,7 @@ class DetailView(LoginRequiredMixin, TemplateView):
...
@@ -103,7 +102,7 @@ class DetailView(LoginRequiredMixin, TemplateView):
result
=
SettyController
.
getInformation
(
result
=
SettyController
.
getInformation
(
templateId
,
hostname
)
templateId
,
hostname
)
return
JsonResponse
(
result
)
return
JsonResponse
(
result
)
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment