Skip to content

Commit

Permalink
[ci][win32] Add basic integration tests (#2725)
Browse files Browse the repository at this point in the history
For the following Windows checks:

* iis
* sqlserver
* windows_service
* wmi_check

The tests are meant to run on AppVeyor and make heavy use of the environment provided by AppVeyor. They would most likely fail if run on a local dev environment on Windows.
  • Loading branch information
olivielpeau committed Aug 3, 2016
1 parent 489902e commit e4a22e5
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 82 deletions.
12 changes: 12 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,33 @@ environment:
PYTHON_VERSION: 2.7.9
PYTHON_ARCH: 32
PYWIN32_URL: https://downloads.sourceforge.net/project/pywin32/pywin32/Build%20219/pywin32-219.win32-py2.7.exe
PYWIN32_INSTALL_DIR: pywin32-219-py2.7-win32.egg
- PYTHON: C:\\Python27-x64
PYTHON_VERSION: 2.7.9
PYTHON_ARCH: 64
PYWIN32_URL: http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win-amd64-py2.7.exe/download
PYWIN32_INSTALL_DIR: pywin32-219-py2.7-win-amd64.egg
cache:
- C:\projects\dd-agent\.cache
- C:\projects\dd-agent\vendor\cache
- C:\projects\dd-agent\embedded
services:
- iis
- mssql2008r2sp2
- mssql2012sp1
- mssql2014
install:
# Use the 64-bit ruby so that all the Powershell classes are accessible when running shell commands from ruby
- set PATH=C:\Ruby22-x64\bin;%PATH%
- gem install bundler --quiet --no-ri --no-rdoc
- bundle install
- bundle package
- if not exist %PIP_CACHE% mkdir %PIP_CACHE%
- ps: If (-Not (Test-Path $env:PYWIN_PATH)) {(new-object net.webclient).DownloadFile("$env:PYWIN32_URL", "$env:PYWIN_PATH")}
- "%PYTHON%/Scripts/easy_install.exe %PYWIN_PATH%"
# Remove the adodbapi module shipped with pywin32: it conflicts with the pip-installed adodbapi
- ps: rm $env:PYTHON/lib/site-packages/$env:PYWIN32_INSTALL_DIR/adodbapi/__init__.py
- ps: rm $env:PYTHON/lib/site-packages/$env:PYWIN32_INSTALL_DIR/adodbapi/__init__.pyc
build: off
test_script:
- set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
Expand Down
10 changes: 9 additions & 1 deletion ci/windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@

task install: ['ci:common:install']

task before_script: ['ci:common:before_script']
task before_script: ['ci:common:before_script'] do
# Set up an IIS website
site_name = 'Test-Website-1'
site_folder = File.join(ENV['INTEGRATIONS_DIR'], "iis_#{site_name}")
sh %(powershell New-Item -ItemType Directory -Force #{site_folder})
sh %(powershell Import-Module WebAdministration)
# Create the new website
sh %(powershell New-Website -Name #{site_name} -Port 8080 -PhysicalPath #{site_folder})
end

task script: ['ci:common:script'] do
this_provides = [
Expand Down
86 changes: 86 additions & 0 deletions tests/checks/integration/test_iis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 3p
from nose.plugins.attrib import attr

# project
from tests.checks.common import AgentCheckTest

MINIMAL_INSTANCE = {
'host': '.',
}

INSTANCE = {
'host': '.',
'sites': ['Default Web Site', 'Test-Website-1', 'Non Existing Website'],
}

INVALID_HOST_INSTANCE = {
'host': 'nonexistinghost'
}


@attr('windows')
@attr(requires='windows')
class IISTest(AgentCheckTest):
CHECK_NAME = 'iis'

IIS_METRICS = (
'iis.uptime',
# Network
'iis.net.bytes_sent',
'iis.net.bytes_rcvd',
'iis.net.bytes_total',
'iis.net.num_connections',
'iis.net.files_sent',
'iis.net.files_rcvd',
'iis.net.connection_attempts',
# HTTP Methods
'iis.httpd_request_method.get',
'iis.httpd_request_method.post',
'iis.httpd_request_method.head',
'iis.httpd_request_method.put',
'iis.httpd_request_method.delete',
'iis.httpd_request_method.options',
'iis.httpd_request_method.trace',
# Errors
'iis.errors.not_found',
'iis.errors.locked',
# Users
'iis.users.anon',
'iis.users.nonanon',
# Requests
'iis.requests.cgi',
'iis.requests.isapi',
)

def test_basic_check(self):
self.run_check_twice({'instances': [MINIMAL_INSTANCE]})

for metric in self.IIS_METRICS:
self.assertMetric(metric, tags=[], count=1)

self.assertServiceCheckOK('iis.site_up', tags=["site:{0}".format('Total')], count=1)
self.coverage_report()

def test_check_on_specific_websites(self):
self.run_check_twice({'instances': [INSTANCE]})

site_tags = ['Default_Web_Site', 'Test_Website_1']
for metric in self.IIS_METRICS:
for site_tag in site_tags:
self.assertMetric(metric, tags=["site:{0}".format(site_tag)], count=1)

self.assertServiceCheckOK('iis.site_up',
tags=["site:{0}".format('Default_Web_Site')], count=1)
self.assertServiceCheckOK('iis.site_up',
tags=["site:{0}".format('Test_Website_1')], count=1)
self.assertServiceCheckCritical('iis.site_up',
tags=["site:{0}".format('Non_Existing_Website')], count=1)

self.coverage_report()

def test_service_check_with_invalid_host(self):
self.run_check({'instances': [INVALID_HOST_INSTANCE]})

self.assertServiceCheckCritical('iis.site_up', tags=["site:{0}".format('Total')])

self.coverage_report()
122 changes: 122 additions & 0 deletions tests/checks/integration/test_sqlserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# stdlib
import copy

# 3p
from nose.plugins.attrib import attr

# project
from tests.checks.common import AgentCheckTest


"""
Runs against AppVeyor's SQLServer setups with their default configurations
"""

CONFIG = {
'init_config': {
'custom_metrics': [
{
'name': 'sqlserver.clr.execution',
'type': 'gauge',
'counter_name': 'CLR Execution',
},
{
'name': 'sqlserver.exec.in_progress',
'type': 'gauge',
'counter_name': 'OLEDB calls',
'instance_name': 'Cumulative execution time (ms) per second',
},
{
'name': 'sqlserver.db.commit_table_entries',
'type': 'gauge',
'counter_name': 'Log Flushes/sec',
'instance_name': 'ALL',
'tag_by': 'db',
},
],
}
}

SQL2008_INSTANCE = {
'host': '(local)\SQL2008R2SP2',
'username': 'sa',
'password': 'Password12!',
}

SQL2012_INSTANCE = {
'host': '(local)\SQL2012SP1',
'username': 'sa',
'password': 'Password12!',
}

SQL2014_INSTANCE = {
'host': '(local)\SQL2014',
'username': 'sa',
'password': 'Password12!',
}

EXPECTED_METRICS = [
'sqlserver.buffer.cache_hit_ratio',
'sqlserver.buffer.page_life_expectancy',
'sqlserver.stats.batch_requests',
'sqlserver.stats.sql_compilations',
'sqlserver.stats.sql_recompilations',
'sqlserver.stats.connections',
'sqlserver.stats.lock_waits',
'sqlserver.access.page_splits',
'sqlserver.stats.procs_blocked',
'sqlserver.buffer.checkpoint_pages',
]


@attr('windows')
@attr(requires='windows')
class SQLServerTest(AgentCheckTest):
CHECK_NAME = 'sqlserver'

def _test_check(self, config):
self.run_check_twice(config, force_reload=True)

# Check our custom metrics
self.assertMetric('sqlserver.clr.execution')
self.assertMetric('sqlserver.exec.in_progress')
# Make sure the ALL custom metric is tagged by db
self.assertMetricTagPrefix('sqlserver.db.commit_table_entries', tag_prefix='db')

for metric in EXPECTED_METRICS:
self.assertMetric(metric, count=1)

self.assertServiceCheckOK('sqlserver.can_connect',
tags=['host:{}'.format(config['instances'][0]['host']), 'db:master'])

self.coverage_report()

def test_check_2008(self):
config = copy.deepcopy(CONFIG)
config['instances'] = [SQL2008_INSTANCE]
self._test_check(config)

def test_check_2012(self):
config = copy.deepcopy(CONFIG)
config['instances'] = [SQL2012_INSTANCE]
self._test_check(config)

def test_check_2014(self):
config = copy.deepcopy(CONFIG)
config['instances'] = [SQL2014_INSTANCE]
self._test_check(config)

def test_check_no_connection(self):
config = copy.deepcopy(CONFIG)
config['instances'] = [{
'host': '(local)\SQL2012SP1',
'username': 'sa',
'password': 'InvalidPassword',
'timeout': 1,
}]

with self.assertRaisesRegexp(Exception, 'Unable to connect to SQL Server'):
self.run_check(config, force_reload=True)

self.assertServiceCheckCritical('sqlserver.can_connect',
tags=['host:(local)\SQL2012SP1', 'db:master'])
35 changes: 35 additions & 0 deletions tests/checks/integration/test_windows_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 3p
from nose.plugins.attrib import attr

# project
from tests.checks.common import AgentCheckTest

INSTANCE = {
'host': '.',
'services': ['EventLog', 'Dnscache', 'NonExistingService'],
}

INVALID_HOST_INSTANCE = {
'host': 'nonexistinghost',
'services': ['EventLog'],
}


@attr('windows')
@attr(requires='windows')
class WindowsServiceTest(AgentCheckTest):
CHECK_NAME = 'windows_service'

SERVICE_CHECK_NAME = 'windows_service.state'

def test_basic_check(self):
self.run_check({'instances': [INSTANCE]})
self.assertServiceCheckOK(self.SERVICE_CHECK_NAME, tags=['service:EventLog'], count=1)
self.assertServiceCheckOK(self.SERVICE_CHECK_NAME, tags=['service:Dnscache'], count=1)
self.assertServiceCheckCritical(self.SERVICE_CHECK_NAME, tags=['service:NonExistingService'], count=1)
self.coverage_report()

def test_invalid_host(self):
self.run_check({'instances': [INVALID_HOST_INSTANCE]})
self.assertServiceCheckCritical(self.SERVICE_CHECK_NAME, tags=['host:nonexistinghost', 'service:EventLog'], count=1)
self.coverage_report()
91 changes: 91 additions & 0 deletions tests/checks/integration/test_wmi_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# stdlib
import copy

# 3p
from mock import Mock
from nose.plugins.attrib import attr

# project
from tests.checks.common import AgentCheckTest

INSTANCE = {
'class': 'Win32_PerfFormattedData_PerfProc_Process',
'metrics': [
['ThreadCount', 'proc.threads.count', 'gauge'],
['IOReadBytesPerSec', 'proc.io.bytes_read', 'gauge'],
['VirtualBytes', 'proc.mem.virtual', 'gauge'],
['PercentProcessorTime', 'proc.cpu_pct', 'gauge'],
],
'tag_by': 'Name',
}

INSTANCE_METRICS = [
'proc.threads.count',
'proc.io.bytes_read',
'proc.mem.virtual',
'proc.cpu_pct',
]


@attr('windows')
@attr(requires='windows')
class WMICheckTest(AgentCheckTest):
CHECK_NAME = 'wmi_check'

def test_basic_check(self):
instance = copy.deepcopy(INSTANCE)
instance['filters'] = [{'Name': 'svchost'}]
self.run_check({'instances': [instance]})

for metric in INSTANCE_METRICS:
self.assertMetric(metric, tags=['name:svchost'], count=1)

self.coverage_report()

def test_check_with_wildcard(self):
instance = copy.deepcopy(INSTANCE)
instance['filters'] = [{'Name': 'svchost%'}]
self.run_check({'instances': [instance]})

for metric in INSTANCE_METRICS:
# We can assume that at least 2 svchost processes are running
self.assertMetric(metric, tags=['name:svchost'], count=1)
self.assertMetric(metric, tags=['name:svchost#1'], count=1)

def test_check_with_tag_queries(self):
instance = copy.deepcopy(INSTANCE)
instance['filters'] = [{'Name': 'svchost%'}]
instance['tag_queries'] = [['IDProcess', 'Win32_Process', 'Handle', 'CommandLine']]
self.run_check({'instances': [instance]})

for metric in INSTANCE_METRICS:
# No instance "number" (`#`) when tag_queries is specified
self.assertMetricTag(metric, tag='name:svchost#1', count=0)
self.assertMetricTag(metric, tag='name:svchost')
self.assertMetricTagPrefix(metric, tag_prefix='commandline:')

def test_invalid_class(self):
instance = copy.deepcopy(INSTANCE)
instance['class'] = 'Unix'
logger = Mock()

self.run_check({'instances': [instance]}, mocks={'log': logger})

# A warning is logged
self.assertEquals(logger.warning.call_count, 1)

# No metrics/service check
self.coverage_report()

def test_invalid_metrics(self):
instance = copy.deepcopy(INSTANCE)
instance['metrics'].append(['InvalidProperty', 'proc.will.not.be.reported', 'gauge'])
logger = Mock()

self.run_check({'instances': [instance]}, mocks={'log': logger})

# A warning is logged
self.assertEquals(logger.warning.call_count, 1)

# No metrics/service check
self.coverage_report()
Loading

0 comments on commit e4a22e5

Please sign in to comment.