def test_parse_complex(self): cfg = NginxConfigParser(complex_config) tree = cfg.simplify() indexed_tree = cfg.tree # common structure assert_that(tree, has_key('http')) assert_that(tree, has_key('events')) # http http = tree['http'] assert_that(http, has_key('server')) assert_that(http, has_key('upstream')) assert_that(http, has_key('include')) assert_that(http['server'], is_(instance_of(list))) assert_that(http['server'], has_length(11)) # upstream upstream = http['upstream'] assert_that(upstream, has_length(2)) # ifs for server in http['server']: if server.get('listen', '') == '127.0.0.3:10122': assert_that(server, has_item('if')) # check index tree x1_location_index = indexed_tree['http'][0]['server'][0][0]['location']['/'][1] x2_return_index = indexed_tree['http'][0]['server'][1][0]['location']['/'][0]['return'][1] assert_that(cfg.index[x1_location_index], equal_to((0, 8))) # root file, line number 8 assert_that(cfg.index[x2_return_index], equal_to((0, 9))) # root file, line number 9
def test_parse_ssl(self): """ This test case specifically checks to see that none of the excluded directives (SSL focused) are parsed. """ cfg = NginxConfigParser(ssl_config) tree = cfg.simplify() assert_that(tree, has_key('server')) # ssl for directive in IGNORED_DIRECTIVES: assert_that(tree['server'][1], is_not(has_item(directive)))
def test_parse_rewrites(self): cfg = NginxConfigParser(rewrites_config) tree = cfg.simplify() # common structure assert_that(tree, has_key('http')) # http http = tree['http'] assert_that(http, has_key('server')) # rewrites for server in http['server']: if server.get('server_name', '') == 'mb.some.org localhost melchior melchior.some.org': assert_that(server, has_item('rewrite'))
def test_parse_simple(self): cfg = NginxConfigParser(simple_config) tree = cfg.simplify() indexed_tree = cfg.tree # common structure assert_that(tree, has_key('http')) assert_that(tree, has_key('events')) # http http = tree['http'] assert_that(http, has_key('server')) assert_that(http, has_key('types')) assert_that(http, has_key('include')) assert_that(http['server'], is_(instance_of(list))) assert_that(http['server'], has_length(2)) # server server = http['server'][1] assert_that(server, has_key('listen')) assert_that(server, has_key('location')) assert_that(server['location'], is_(instance_of(dict))) # location location = server['location'] assert_that(location, has_key('/basic_status')) # nested location assert_that(http['server'][0]['location']['/'], has_key('location')) # included mimes mimes = http['types'] assert_that(mimes, has_key('application/java-archive')) # check index tree worker_connections_index = indexed_tree['events'][0]['worker_connections'][1] basic_status_index = indexed_tree['http'][0]['server'][1][0]['location']['/basic_status'][1] stub_status_in_basic_index = indexed_tree['http'][0]['server'][1][0]['location']['/basic_status'][0]['stub_status'][1] proxy_pass_index = indexed_tree['http'][0]['server'][0][0]['location']['/'][0]['proxy_pass'][1] assert_that(cfg.index[worker_connections_index], equal_to((0, 6))) # root file, line number 6 assert_that(cfg.index[basic_status_index], equal_to((0, 67))) # root file, line number 65 assert_that(cfg.index[stub_status_in_basic_index], equal_to((0, 69))) # root file, line number 66 assert_that(cfg.index[proxy_pass_index], equal_to((2, 13))) # third loaded file, line number 13
def test_parse_map_lua_perl(self): cfg = NginxConfigParser(map_lua_perl) tree = cfg.simplify() # common structure assert_that(tree, has_key('http')) # http http = tree['http'] assert_that(http, has_key('server')) assert_that(http, has_key('map')) assert_that(http, has_key('perl_set')) # lua for server in http['server']: if server.get('server_name', '') == '127.0.0.1': assert_that(server, has_item('lua_shared_dict')) for location, data in server['location'].iteritems(): if location == '= /some/': assert_that(data, has_item('rewrite_by_lua'))
def test_parse_huge(self): cfg = NginxConfigParser(huge_config) tree = cfg.simplify() indexed_tree = cfg.tree # common structure assert_that(tree, has_key('http')) assert_that(tree, has_key('events')) # http http = tree['http'] assert_that(http, has_key('server')) assert_that(http, has_key('include')) assert_that(http['server'], is_(instance_of(list))) assert_that(http['server'], has_length(8)) # map http_map = http['map'] assert_that(http_map, equal_to({'$dirname $diruri': {'default': '"dirindex.html"', 'include': ['"dir.map"']}})) # check index tree books_location_index = indexed_tree['http'][0]['server'][2][0]['location']['/books/'][1] assert_that(cfg.index[books_location_index], equal_to((0, 134))) # root file, line number 134
class NginxConfig(object): """ Nginx config representation Parses configs with all includes, etc Main tasks: - find all log formats - find all access logs - find all error logs - find stub_status url """ def __init__(self, filename, binary=None, prefix=None): self.filename = filename self.binary = binary self.prefix = prefix self.log_formats = {} self.access_logs = {} self.error_logs = [] self.stub_status = [] self.plus_status = [] self.parser = NginxConfigParser(filename) # initial parsing self.tree = self.parser.tree self.files = self.parser.files self.index = self.parser.index self.parser_errors = self.parser.errors self.test_errors = [] # go through and collect all logical data self.__recursive_search(subtree=self.parser.simplify()) # try to locate and use default logs (PREFIX/logs/*) self.add_default_logs() def __recursive_search(self, subtree=None, ctx=None): """ Searches needed data in config's tree :param subtree: dict with tree to parse :param ctx: dict with context """ ctx = ctx if ctx is not None else {} subtree = subtree if subtree is not None else {} for key, value in subtree.iteritems(): if key == "error_log": error_logs = value if isinstance(value, list) else [value] for er_log_definition in error_logs: if er_log_definition == "off": continue log_name = er_log_definition.split(" ")[0] log_name = re.sub("['\"]", "", log_name) # remove all ' and " if log_name.startswith("syslog"): continue elif not log_name.startswith("/"): log_name = "%s/%s" % (self.prefix, log_name) if log_name not in self.error_logs: self.error_logs.append(log_name) elif key == "access_log": access_logs = value if isinstance(value, list) else [value] for ac_log_definition in access_logs: if ac_log_definition == "off": continue parts = filter(lambda x: x, ac_log_definition.split(" ")) log_format = None if len(parts) == 1 else parts[1] log_name = parts[0] log_name = re.sub("['\"]", "", log_name) # remove all ' and " if log_name.startswith("syslog"): continue elif not log_name.startswith("/"): log_name = "%s/%s" % (self.prefix, log_name) self.access_logs[log_name] = log_format elif key == "log_format": for k, v in value.iteritems(): self.log_formats[k] = v elif key == "server" and isinstance(value, list) and "upstream" not in ctx: for server in value: current_ctx = copy.copy(ctx) if server.get("listen") is None: # if no listens specified, then use default *:80 and *:8000 listen = ["80", "8000"] else: listen = server.get("listen") listen = listen if isinstance(listen, list) else [listen] ctx["ip_port"] = [] for item in listen: listen_first_part = item.split(" ")[0] addr, port = self.__parse_listen(listen_first_part) if addr in ("*", "0.0.0.0"): addr = "127.0.0.1" elif addr == "[::]": addr = "[::1]" ctx["ip_port"].append((addr, port)) if "server_name" in server: ctx["server_name"] = server.get("server_name") self.__recursive_search(subtree=server, ctx=ctx) ctx = current_ctx elif key == "upstream": for upstream, upstream_info in value.iteritems(): current_ctx = copy.copy(ctx) ctx["upstream"] = upstream self.__recursive_search(subtree=upstream_info, ctx=ctx) ctx = current_ctx elif key == "location": for location, location_info in value.iteritems(): current_ctx = copy.copy(ctx) ctx["location"] = location self.__recursive_search(subtree=location_info, ctx=ctx) ctx = current_ctx elif key == "stub_status" and ctx and "ip_port" in ctx: for url in self.__status_url(ctx): if url not in self.stub_status: self.stub_status.append(url) elif key == "status" and ctx and "ip_port" in ctx: for url in self.__status_url(ctx, server_preferred=True): if url not in self.plus_status: self.plus_status.append(url) elif isinstance(value, dict): self.__recursive_search(subtree=value, ctx=ctx) elif isinstance(value, list): for next_subtree in value: if isinstance(next_subtree, dict): self.__recursive_search(subtree=next_subtree, ctx=ctx) def __status_url(self, ctx, server_preferred=False): result = [] location = ctx.get("location", "/") # remove all modifiers location_parts = location.split(" ") final_location_part = location_parts[-1] for ip_port in ctx.get("ip_port"): addr, port = ip_port if server_preferred and "server_name" in ctx: if isinstance(ctx["server_name"], list): addr = ctx["server_name"][0].split(" ")[0] else: addr = ctx["server_name"].split(" ")[0] result.append("%s:%s%s" % (addr, port, final_location_part)) return result def run_test(self): """ Tests the configuration using nginx -t Saves event info if syntax check was not successful """ start_time = time.time() context.log.info("running %s -t -c %s" % (self.binary, self.filename)) if self.binary: try: _, nginx_t_err = subp.call("%s -t -c %s" % (self.binary, self.filename), check=False) for line in nginx_t_err: if "syntax is" in line and "syntax is ok" not in line: self.test_errors.append(line) except Exception, e: exception_name = e.__class__.__name__ context.log.error("failed to %s -t -c %s due to %s" % (self.binary, self.filename, exception_name)) context.log.debug("additional info:", exc_info=True) end_time = time.time() return end_time - start_time