Skip to content

Commit

Permalink
adding config-handlrs
Browse files Browse the repository at this point in the history
  • Loading branch information
advornic committed Jan 30, 2015
1 parent 20329d3 commit f4b1242
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 24 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def join_url(x, y):
data_files = []
# configuration folders are not cleared on upgrade/downgrade
for folder in ['nodes', 'definitions', 'files', 'resources',
'bootstrap']:
'bootstrap', 'config-handlers']:
path = '%s/%s' % (install_path, folder)
if install() and not os.path.isdir(path):
if os.path.exists(path):
Expand Down
2 changes: 1 addition & 1 deletion test/server/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ def test_post_node_success(self, m_load_neighbordb, m_repository):
# 'definition' is not written to the pattern file
# Empty 'variables', 'node' are not written to the
# pattern file either
self.assertEqual(sorted(write_mock.call_args[0][0].keys()),
self.assertEqual(sorted(write_mock.call_args_list[1][0][0].keys()),
['interfaces', 'name'])

location = 'http://localhost/nodes/%s' % node.serialnumber
Expand Down
7 changes: 5 additions & 2 deletions test/server/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,16 @@ def test_add_folder_failure(self, m_makedirs):
self.assertRaises(RepositoryError, store.add_folder, random_string())

@patch('ztpserver.repository.FileObject')
def test_create_file_success(self, m_fileobj):
@patch('os.chmod')
def test_create_file_success(self, _, m_fileobj):
store = Repository(random_string())
store.add_file(random_string())
self.assertFalse(m_fileobj.return_value.write.called)

@patch('ztpserver.repository.FileObject')
def test_create_file_with_contents_success(self, m_fileobj):
@patch('os.chmod')
def test_create_file_with_contents_success(self, _,
m_fileobj):
store = Repository(random_string())
store.add_file(random_string(), random_string())
self.assertTrue(m_fileobj.return_value.write.called)
Expand Down
2 changes: 1 addition & 1 deletion test/server/test_topology_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def test_create_pattern_with_kwargs(self):
self.assertEqual(1, len(obj.interfaces))

def test_add_interface(self):
obj = Pattern(None, None, [{'Ethernet1': 'any'}])
obj = Pattern(interfaces=[{'Ethernet1': 'any'}])
self.assertEqual(len(obj.interfaces), 1)


Expand Down
74 changes: 67 additions & 7 deletions ztpserver/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import logging
import os
import routes
import subprocess

from string import Template
from subprocess import PIPE
from webob.static import FileApp

from ztpserver.constants import HTTP_STATUS_NOT_FOUND, HTTP_STATUS_CREATED
Expand All @@ -57,6 +59,7 @@


DEFINITION_FN = 'definition'
CONFIG_HANDLER_FN = 'config-handler'
STARTUP_CONFIG_FN = 'startup-config'
PATTERN_FN = 'pattern'
NODE_FN = '.node'
Expand Down Expand Up @@ -205,26 +208,51 @@ def get_config(self, request, resource, **kwargs):
#-------------------------------------------------------------------

def put_config(self, request, **kwargs):
node_id = kwargs['resource']

log.debug('%s: startup-config PUT request: \n%s\n' %
(kwargs['resource'], request))
(node_id, request))

fobj = None
try:
body = str(request.body)
content_type = str(request.content_type)
filename = self.expand(kwargs['resource'], STARTUP_CONFIG_FN)
filename = self.expand(node_id, STARTUP_CONFIG_FN)
fobj = self.repository.get_file(filename)
except FileObjectNotFound:
log.debug('%s: file not found: %s (adding it)' %
(kwargs['resource'], filename))
(node_id, filename))
fobj = self.repository.add_file(filename)
finally:
if fobj:
fobj.write(body, content_type)
else:
log.error('%s: unable to write %s' %
(kwargs['resource'], filename))
(node_id, filename))
return self.http_bad_request()

# Execute event-handler
script = self.expand(node_id, CONFIG_HANDLER_FN)
if os.path.isfile(script):
proc = subprocess.Popen(script, stdin=PIPE,
stdout=PIPE, stderr=PIPE,
shell=True)
code = proc.returncode #pylint: disable=E1101
(out, err) = proc.communicate()
if code or err:
log.warn('Startup-config saved for %s '
'(%s failed: return code=%s, stderr=%s)' %
(node_id, script, code, err))
log.debug('%s output: \n%s' % (script, out))
else:
log.info('Startup-config saved for %s '
'(%s executed successfully)' %
(node_id, script))
log.debug('%s output: \n%s' % (script, out))
else:
log.info('Startup-config saved for %s (no config-handler)' %
node_id)

return {}

#-------------------------------------------------------------------
Expand Down Expand Up @@ -383,8 +411,9 @@ def post_node(self, response, *args, **kwargs):

log.info('%s: node matched \'%s\' pattern in neighbordb' %
(node_id, match.name))
try:

# Load definition
try:
definition_url = self.expand(match.definition,
folder='definitions')
fobj = self.repository.get_file(definition_url)
Expand All @@ -404,20 +433,45 @@ def post_node(self, response, *args, **kwargs):

definition_fn = self.expand(node_id, DEFINITION_FN)

# Load config-handler
if match.config_handler:
try:
config_handler_url = self.expand(match.config_handler,
folder='config-handlers')
fobj = self.repository.get_file(config_handler_url)
log.info('%s: node config-handler copied from: %s' %
(node_id, config_handler_url))
except FileObjectNotFound:
log.error('%s: failed to find config-handler (%s)' %
(node_id, config_handler_url))
raise

try:
config_handler = fobj.read(content_type=CONTENT_TYPE_OTHER)
except FileObjectError:
log.error('%s: failed to load config-handler' %
(node_id))
raise

config_handler_fn = self.expand(node_id, CONFIG_HANDLER_FN)

# Create node folder
self.repository.add_folder(self.expand(node_id))

log.info('%s: new dynamically-provisioned node created: /nodes/%s' %
(node_id, node_id))

# Add definition
fobj = self.repository.add_file(definition_fn)
fobj.write(definition, CONTENT_TYPE_YAML)


# Add pattern
pattern_fn = self.expand(node_id, PATTERN_FN)
fobj = self.repository.add_file(pattern_fn)

pattern = match.serialize()

# No need to write the definition name in the apttern file
# No need to write the definition name in the pattern file
del pattern['definition']

for attr in ['node', 'variables']:
Expand All @@ -429,6 +483,12 @@ def post_node(self, response, *args, **kwargs):
pattern['interfaces'] = [{'any': {'any': 'any'}}]

fobj.write(pattern, CONTENT_TYPE_YAML)

# Add config-handler
if match.config_handler:
fobj = self.repository.add_file(config_handler_fn)
fobj.write(config_handler, CONTENT_TYPE_OTHER)

response['status'] = HTTP_STATUS_CREATED
return (response, 'dump_node')

Expand Down
1 change: 1 addition & 0 deletions ztpserver/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def add_file(self, file_path, contents=None, content_type=None):
obj = FileObject(file_path)
if contents:
obj.write(contents, content_type)
os.chmod(file_path, 0774)
return obj

def exists(self, file_path):
Expand Down
12 changes: 8 additions & 4 deletions ztpserver/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
# pylint: disable=R0201
#

from ztpserver.constants import CONTENT_TYPE_OTHER
from ztpserver.constants import CONTENT_TYPE_JSON
from ztpserver.constants import CONTENT_TYPE_YAML

import collections
import logging
import json

import threading
import yaml

Expand Down Expand Up @@ -137,9 +141,9 @@ def __init__(self, node_id):
self.node_id = node_id

self._handlers = {
'text/plain': TextSerializer(self.node_id),
'application/json': JSONSerializer(self.node_id),
'application/yaml': YAMLSerializer(self.node_id)
CONTENT_TYPE_OTHER: TextSerializer(self.node_id),
CONTENT_TYPE_JSON: JSONSerializer(self.node_id),
CONTENT_TYPE_YAML: YAMLSerializer(self.node_id)
}

@property
Expand Down
19 changes: 11 additions & 8 deletions ztpserver/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,9 @@ def load_pattern(pattern, content_type=CONTENT_TYPE_YAML, node_id=None):
node_id)

# add dummy values to pass validation
if 'definition' not in pattern:
pattern['definition'] = 'definition'
if 'name' not in pattern:
pattern['name'] = 'name'
for dummy in ['definition', 'name', 'config_handler']:
if dummy not in pattern:
pattern[dummy] = dummy

if not validate_pattern(pattern, node_id):
log.error('%s: failed to validate pattern attributes' % node_id)
Expand Down Expand Up @@ -374,6 +373,7 @@ def add_pattern(self, name, **kwargs):

kwargs['node'] = kwargs.get('node')
kwargs['definition'] = kwargs.get('definition')
kwargs['config_handler'] = kwargs.get('config-handler')
kwargs['interfaces'] = kwargs.get('interfaces', list())
kwargs['variables'] = kwargs.get('variables', dict())

Expand Down Expand Up @@ -479,11 +479,13 @@ def match_node(self, node):

class Pattern(object):

def __init__(self, name=None, definition=None, interfaces=None,
def __init__(self, name=None, definition=None,
config_handler=None, interfaces=None,
node=None, variables=None, node_id=None):

self.name = name
self.definition = definition
self.config_handler = config_handler

self.node = node
self.node_id = node_id
Expand Down Expand Up @@ -520,9 +522,10 @@ def variable_substitution(self):
(self.node_id, self.name, str(exc)))

def serialize(self):
data = dict(name=self.name, definition=self.definition)
data['variables'] = self.variables
data['node'] = self.node
data = dict(name=self.name, definition=self.definition,
variables=self.variables, node=self.node)

data['config-handler'] = self.config_handler

interfaces = []
for item in self.interfaces:
Expand Down

0 comments on commit f4b1242

Please sign in to comment.