summaryrefslogtreecommitdiffstats
path: root/tests/inspector.py
blob: 7195969a7827b0aa66cf461c1938c962d57ed508 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
##############################################################################
# Copyright (c) 2016 NEC Corporation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################

import argparse
import collections
from flask import Flask
from flask import request
import json
import logger as doctor_log
import os
import threading
import time

import novaclient.client as novaclient

import nova_force_down

LOG = doctor_log.Logger('doctor_inspector').getLogger()


class ThreadedResetState(threading.Thread):

    def __init__(self, nova, state, server):
        threading.Thread.__init__(self)
        self.nova = nova
        self.state = state
        self.server = server

    def run(self):
        self.nova.servers.reset_state(self.server, self.state)
        LOG.info('doctor mark vm(%s) error at %s' % (self.server, time.time()))


class DoctorInspectorSample(object):

    NOVA_API_VERSION = '2.11'
    NUMBER_OF_CLIENTS = 50
    # TODO(tojuvone): This could be enhanced in future with dynamic
    # reuse of self.novaclients when all threads in use and
    # self.NUMBER_OF_CLIENTS based on amount of cores or overriden by input
    # argument

    def __init__(self):
        self.servers = collections.defaultdict(list)
        self.novaclients = list()
        # Pool of novaclients for redundant usage
        for i in range(self.NUMBER_OF_CLIENTS):
            self.novaclients.append(novaclient.Client(self.NOVA_API_VERSION,
                                    os.environ['OS_USERNAME'],
                                    os.environ['OS_PASSWORD'],
                                    os.environ['OS_TENANT_NAME'],
                                    os.environ['OS_AUTH_URL'],
                                    connection_pool=True))
        # Normally we use this client for non redundant API calls
        self.nova=self.novaclients[0]
        self.nova.servers.list(detailed=False)
        self.init_servers_list()

    def init_servers_list(self):
        opts = {'all_tenants': True}
        servers=self.nova.servers.list(search_opts=opts)
        self.servers.clear()
        for server in servers:
            try:
                host=server.__dict__.get('OS-EXT-SRV-ATTR:host')
                self.servers[host].append(server)
                LOG.debug('get hostname=%s from server=%s' % (host, server))
            except Exception as e:
                LOG.error('can not get hostname from server=%s' % server)

    def disable_compute_host(self, hostname):
        threads = []
        if len(self.servers[hostname]) > self.NUMBER_OF_CLIENTS:
            # TODO(tojuvone): This could be enhanced in future with dynamic
            # reuse of self.novaclients when all threads in use
            LOG.error('%d servers in %s. Can handle only %d'%(
                      self.servers[hostname], hostname, self.NUMBER_OF_CLIENTS))
        for nova, server in zip(self.novaclients, self.servers[hostname]):
            t = ThreadedResetState(nova, "error", server)
            t.start()
            threads.append(t)
        for t in threads:
            t.join()
        # NOTE: We use our own client here instead of this novaclient for a
        #       workaround.  Once keystone provides v2.1 nova api endpoint
        #       in the service catalog which is configured by OpenStack
        #       installer, we can use this:
        #
        # self.nova.services.force_down(hostname, 'nova-compute', True)
        #
        nova_force_down.force_down(hostname)
        LOG.info('doctor mark host(%s) down at %s' % (hostname, time.time()))


app = Flask(__name__)
inspector = DoctorInspectorSample()


@app.route('/events', methods=['POST'])
def event_posted():
    LOG.info('event posted at %s' % time.time())
    LOG.info('inspector = %s' % inspector)
    LOG.info('received data = %s' % request.data)
    d = json.loads(request.data)
    for event in d:
        hostname = event['details']['hostname']
        event_type = event['type']
        if event_type == 'compute.host.down':
            inspector.disable_compute_host(hostname)
    return "OK"


def get_args():
    parser = argparse.ArgumentParser(description='Doctor Sample Inspector')
    parser.add_argument('port', metavar='PORT', type=int, nargs='?',
                        help='a port for inspector')
    return parser.parse_args()


def main():
    args = get_args()
    app.run(port=args.port)


if __name__ == '__main__':
    main()
="p">(self): self.validate_get_request(environment_configs.URL, params={ "distribution_version": environment_configs.WRONG_DIST_VER }, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_distribution(self, read): config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_DISTRIBUTION config_response = \ environment_configs.ENV_CONFIGS_WITH_SPECIFIC_DISTRIBUTION_RESPONSE self.validate_get_request(environment_configs.URL, params={ "distribution": environment_configs. CORRECT_DISTRIBUTION }, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_wrong_mechanism_driver(self): config = environment_configs.WRONG_MECHANISM_DRIVER self.validate_get_request(environment_configs.URL, params={"mechanism_drivers": config}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_mechanism_driver(self, read): mechanism = environment_configs.CORRECT_MECHANISM_DRIVER config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_MECHANISM_DRIVER config_response = environment_configs.\ ENV_CONFIGS_WITH_SPECIFIC_MECHANISM_DRIVER_RESPONSE self.validate_get_request(environment_configs.URL, params={"mechanism_drivers": mechanism}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_wrong_type_driver(self): driver = environment_configs.WRONG_TYPE_DRIVER self.validate_get_request(environment_configs.URL, params={"type_drivers": driver}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_type_driver(self, read): driver = environment_configs.CORRECT_TYPE_DRIVER config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_TYPE_DRIVER config_response = environment_configs.\ ENV_CONFIGS_WITH_SPECIFIC_TYPE_DRIVER_RESPONSE self.validate_get_request(environment_configs.URL, params={"type_drivers": driver}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response ) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_user(self, read): config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_USER config_response = \ environment_configs.ENV_CONFIGS_WITH_SPECIFIC_USER_RESPONSE self.validate_get_request(environment_configs.URL, params={"user": environment_configs.USER}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_non_bool_listen(self): self.validate_get_request(environment_configs.URL, params={"listen": environment_configs. NON_BOOL_LISTEN}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_bool_listen(self, read): config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_LISTEN config_response = \ environment_configs.ENV_CONFIGS_WITH_SPECIFIC_LISTEN_RESPONSE self.validate_get_request(environment_configs.URL, params={"listen": environment_configs. BOOL_LISTEN}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_non_bool_scanned(self): self.validate_get_request(environment_configs.URL, params={"scanned": environment_configs. NON_BOOL_SCANNED}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_bool_scanned(self, read): config = environment_configs.ENV_CONFIGS_WITH_SPECIFIC_SCANNED config_response = \ environment_configs.ENV_CONFIGS_WITH_SPECIFIC_SCANNED_RESPONSE self.validate_get_request(environment_configs.URL, params={"scanned": environment_configs. BOOL_SCANNED}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response ) def test_get_env_configs_list_with_non_bool_monitoring_setup_done(self): self.validate_get_request(environment_configs.URL, params={"listen": environment_configs. NON_BOOL_MONITORING_SETUP_DONE}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_bool_monitoring_setup_done(self, read): config = environment_configs.\ ENV_CONFIGS_WITH_SPECIFIC_MONITORING_SETUP_DONE config_response = environment_configs.\ ENV_CONFIGS_WITH_SPECIFIC_MONITORING_SETUP_DONE_RESPONSE self.validate_get_request(environment_configs.URL, params={"scanned": environment_configs. BOOL_MONITORING_SETUP_DONE}, mocks={read: config}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_non_int_page(self): self.validate_get_request(environment_configs.URL, params={"page": base.NON_INT_PAGE}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_int_page(self, read): config_response = environment_configs.ENV_CONFIGS_RESPONSE self.validate_get_request(environment_configs.URL, params={"page": base.INT_PAGE}, mocks={read: environment_configs.ENV_CONFIGS}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_get_environment_configs_list_with_non_int_page_size(self): self.validate_get_request(environment_configs.URL, params={"page_size": base.NON_INT_PAGESIZE}, expected_code=base.BAD_REQUEST_CODE) @patch(base.RESPONDER_BASE_READ) def test_get_environment_configs_list_with_int_page_size(self, read): config_response = environment_configs.ENV_CONFIGS_RESPONSE self.validate_get_request(environment_configs.URL, params={"page_size": base.INT_PAGESIZE}, mocks={read: environment_configs.ENV_CONFIGS}, expected_code=base.SUCCESSFUL_CODE, expected_response=config_response) def test_post_environment_config_without_app_path(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["app_path"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_configuration(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["configuration"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_distribution(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["distribution"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_distribution(self): dist = environment_configs.WRONG_DISTRIBUTION test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={"distribution": dist}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_listen(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["listen"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_listen(self): listen_val = environment_configs.NON_BOOL_LISTEN test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={"listen": listen_val}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_mechanism_driver(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["mechanism_drivers"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_mechanism_driver(self): mechanism = environment_configs.WRONG_MECHANISM_DRIVER test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={ "mechanism_drivers": [mechanism] }) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_name(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["name"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_operational(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["operational"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_scanned(self): scanned_val = environment_configs.NON_BOOL_SCANNED test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={"scanned": scanned_val}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_last_scanned(self): scanned_val = base.WRONG_FORMAT_TIME test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={"last_scanned": scanned_val}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_type(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["type"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_type_drivers(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, deleted_keys=["type_drivers"]) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_wrong_type_drivers(self): driver = environment_configs.WRONG_TYPE_DRIVER test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={"type_drivers": [driver]}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_duplicate_configurations(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG) test_data["configuration"].append({ "name": "OpenStack" }) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_empty_configuration(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG) test_data["configuration"].append({}) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_unknown_configuration(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG) test_data["configuration"].append({ "name": "Unknown configuration", }) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_without_required_configurations(self): for env_type in CONSTANTS_BY_NAMES["environment_types"]: required_conf_list = ( EnvironmentConfigs.REQUIRED_CONFIGURATIONS_NAMES.get(env_type, []) ) if required_conf_list: test_data = \ self.get_updated_data(environment_configs.ENV_CONFIG) test_data['environment_type'] = env_type test_data['configuration'] = [ c for c in test_data['configuration'] if c['name'] != required_conf_list[0] ] self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) def test_post_environment_config_with_incomplete_configuration(self): test_data = self.get_updated_data(environment_configs.ENV_CONFIG, updates={ "configuration": [{ "host": "10.56.20.239", "name": "mysql", "user": "root" }, { "name": "OpenStack", "host": "10.56.20.239", }, { "host": "10.56.20.239", "name": "CLI", "user": "root" }] }) self.validate_post_request(environment_configs.URL, body=json.dumps(test_data), expected_code=base.BAD_REQUEST_CODE) @staticmethod def mock_validate_env_config_with_supported_envs(scanning, monitoring, listening): InventoryMgr.is_feature_supported_in_env = \ lambda self, matches, feature: { EnvironmentFeatures.SCANNING: scanning, EnvironmentFeatures.MONITORING: monitoring, EnvironmentFeatures.LISTENING: listening }[feature] @patch(base.RESPONDER_BASE_WRITE) def test_post_environment_config(self, write): self.mock_validate_env_config_with_supported_envs(True, True, True) post_body = json.dumps(environment_configs.ENV_CONFIG) self.validate_post_request(environment_configs.URL, mocks={ write: None }, body=post_body, expected_code=base.CREATED_CODE) def test_post_unsupported_environment_config(self): test_cases = [ { "scanning": False, "monitoring": True, "listening": True }, { "scanning": True, "monitoring": False, "listening": True }, { "scanning": True, "monitoring": True, "listening": False } ] mock_validate = self.mock_validate_env_config_with_supported_envs config = environment_configs.ENV_CONFIG for test_case in test_cases: mock_validate(test_case["scanning"], test_case["monitoring"], test_case["listening"]) self.validate_post_request(environment_configs.URL, body=json.dumps(config), expected_code=base.BAD_REQUEST_CODE)