From c138dd8f409bb7e653eaeadbd6f9ece7ccab3e82 Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Tue, 12 Feb 2013 15:05:27 -0800 Subject: [PATCH] 1. Added builders support. Each builder is a class dynamically loaded from ./windc/core/builders folder. The class name should be the same as module file name. 2. Updated core/api.py to support datacenter and service creation with extra parameters which are not defined by model explicitly. 3. Added event based approach for the windows environment change. Now when user submits a request to API the core updates database and initiates a new event which defined scope (datacenter, service, VM) and action (add, modify, delete). This event and data will be iterated over all registered builders. Each builder can use this event and data to plan some modification. --- windc/etc/windc-api-paste.ini | 27 +++++++ windc/etc/windc/api-paste.ini | 26 ------- windc/etc/windc/windc.conf | 34 --------- windc/tests/manual/createDataCenter.sh | 4 ++ windc/tests/manual/createDataCenterParameters | 7 ++ windc/tests/manual/createService.sh | 4 ++ windc/tests/manual/createServiceParameters | 8 +++ windc/tests/manual/listDataCenter.sh | 1 + windc/windc/api/v1/datacenters.py | 2 + windc/windc/api/v1/services.py | 1 + windc/windc/core/__init__.py | 5 ++ windc/windc/core/api.py | 56 ++++++++++++++- windc/windc/core/builder.py | 33 +++++++++ windc/windc/core/builder_set.py | 72 +++++++++++++++++++ windc/windc/core/builders/ActiveDirectory.py | 53 ++++++++++++++ windc/windc/core/builders/DataCenter.py | 37 ++++++++++ windc/windc/core/change_events.py | 53 ++++++++++++++ windc/windc/db/api.py | 9 +-- .../versions/001_Add_initial_tables.py | 1 + windc/windc/db/models.py | 1 + windc/windc/drivers/__init__.py | 0 21 files changed, 367 insertions(+), 67 deletions(-) delete mode 100644 windc/etc/windc/api-paste.ini delete mode 100644 windc/etc/windc/windc.conf create mode 100755 windc/tests/manual/createDataCenter.sh create mode 100644 windc/tests/manual/createDataCenterParameters create mode 100755 windc/tests/manual/createService.sh create mode 100644 windc/tests/manual/createServiceParameters create mode 100755 windc/tests/manual/listDataCenter.sh create mode 100644 windc/windc/core/builder.py create mode 100644 windc/windc/core/builder_set.py create mode 100644 windc/windc/core/builders/ActiveDirectory.py create mode 100644 windc/windc/core/builders/DataCenter.py create mode 100644 windc/windc/core/change_events.py create mode 100644 windc/windc/drivers/__init__.py diff --git a/windc/etc/windc-api-paste.ini b/windc/etc/windc-api-paste.ini index 46e1299..7c0deee 100644 --- a/windc/etc/windc-api-paste.ini +++ b/windc/etc/windc-api-paste.ini @@ -1,3 +1,30 @@ +[DEFAULT] +# Show more verbose log output (sets INFO log level output) +verbose = True +# Show debugging output in logs (sets DEBUG log level output) +debug = True +# Address to bind the server to +bind_host = 0.0.0.0 +# Port the bind the server to +bind_port = 8082 +# Log to this file. Make sure the user running skeleton-api has +# permissions to write to this file! +log_file = /tmp/api.log +# Orchestration Adapter Section +# +#provider - Cloud provider to use (openstack, amazon, dummy) +provider = openstack + +# Heat specific parameters +#heat_url - url for the heat service +# [auto] - find in the keystone +heat_url = auto + +#heat_api_version - version of the API to use +# +heat_api_version = 1 + + [pipeline:windc-api] pipeline = apiv1app # NOTE: use the following pipeline for keystone diff --git a/windc/etc/windc/api-paste.ini b/windc/etc/windc/api-paste.ini deleted file mode 100644 index 46e1299..0000000 --- a/windc/etc/windc/api-paste.ini +++ /dev/null @@ -1,26 +0,0 @@ -[pipeline:windc-api] -pipeline = apiv1app -# NOTE: use the following pipeline for keystone -#pipeline = authtoken context apiv1app - -[app:apiv1app] -paste.app_factory = windc.common.wsgi:app_factory -windc.app_factory = windc.api.v1.router:API - -[filter:context] -paste.filter_factory = windc.common.wsgi:filter_factory -windc.filter_factory = windc.common.context:ContextMiddleware - -[filter:authtoken] -paste.filter_factory = keystone.middleware.auth_token:filter_factory -auth_host = 172.18.67.57 -auth_port = 35357 -auth_protocol = http -auth_uri = http://172.18.67.57:5000/v2.0/ -admin_tenant_name = service -admin_user = windc -admin_password = 000 - -[filter:auth-context] -paste.filter_factory = windc.common.wsgi:filter_factory -windc.filter_factory = keystone.middleware.balancer_auth_token:KeystoneContextMiddleware diff --git a/windc/etc/windc/windc.conf b/windc/etc/windc/windc.conf deleted file mode 100644 index 3f1381b..0000000 --- a/windc/etc/windc/windc.conf +++ /dev/null @@ -1,34 +0,0 @@ -[DEFAULT] -# Show more verbose log output (sets INFO log level output) -verbose = True - -# Show debugging output in logs (sets DEBUG log level output) -debug = True - -# Address to bind the server to -bind_host = 0.0.0.0 - -# Port the bind the server to -bind_port = 8082 - -# Log to this file. Make sure the user running skeleton-api has -# permissions to write to this file! -log_file = /tmp/api.log - -[pipeline:windc-api] -pipeline = versionnegotiation context apiv1app - -[pipeline:versions] -pipeline = versionsapp - -[app:versionsapp] -paste.app_factory = windc.api.versions:app_factory - -[app:apiv1app] -paste.app_factory = windc.api.v1:app_factory - -[filter:versionnegotiation] -paste.filter_factory = windc.api.middleware.version_negotiation:filter_factory - -[filter:context] -paste.filter_factory = openstack.common.middleware.context:filter_factory diff --git a/windc/tests/manual/createDataCenter.sh b/windc/tests/manual/createDataCenter.sh new file mode 100755 index 0000000..2c1e531 --- /dev/null +++ b/windc/tests/manual/createDataCenter.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +URL=http://localhost:8082/foo/datacenters +curl -v -H "Content-Type: application/json" -X POST -d@createDataCenterParameters$1 $URL diff --git a/windc/tests/manual/createDataCenterParameters b/windc/tests/manual/createDataCenterParameters new file mode 100644 index 0000000..6200231 --- /dev/null +++ b/windc/tests/manual/createDataCenterParameters @@ -0,0 +1,7 @@ +{ +"name": "Test Data Center 2", +"type": "SingleZone", +"version":"1.1", +"KMS":"172.16.1.2", +"WSUS":"172.16.1.3" +} diff --git a/windc/tests/manual/createService.sh b/windc/tests/manual/createService.sh new file mode 100755 index 0000000..7a50888 --- /dev/null +++ b/windc/tests/manual/createService.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +URL=http://localhost:8082/foo/datacenters/$1/services +curl -v -H "Content-Type: application/json" -X POST -d@createServiceParameters$2 $URL diff --git a/windc/tests/manual/createServiceParameters b/windc/tests/manual/createServiceParameters new file mode 100644 index 0000000..6e9817c --- /dev/null +++ b/windc/tests/manual/createServiceParameters @@ -0,0 +1,8 @@ +{ +"type": "active_directory_service", +"zones": ["zone1"], +"domain": "ACME.cloud", +"AdminUser": "Admin", +"AdminPassword": "StrongPassword", +"DomainControllerNames": ["APP-AD001","APP-AD002"] +} diff --git a/windc/tests/manual/listDataCenter.sh b/windc/tests/manual/listDataCenter.sh new file mode 100755 index 0000000..bb95680 --- /dev/null +++ b/windc/tests/manual/listDataCenter.sh @@ -0,0 +1 @@ +curl -X GET http://localhost:8082/foo/datacenters diff --git a/windc/windc/api/v1/datacenters.py b/windc/windc/api/v1/datacenters.py index eab65e1..2b0e1f9 100644 --- a/windc/windc/api/v1/datacenters.py +++ b/windc/windc/api/v1/datacenters.py @@ -42,6 +42,8 @@ class Controller(object): def index(self, req, tenant_id): LOG.debug("Got index request. Request: %s", req) result = core_api.dc_get_index(self.conf, tenant_id) + LOG.debug("Got list of datacenters: %s", result) + result return {'datacenters': result} @utils.http_success_code(202) diff --git a/windc/windc/api/v1/services.py b/windc/windc/api/v1/services.py index 722c48d..e635250 100644 --- a/windc/windc/api/v1/services.py +++ b/windc/windc/api/v1/services.py @@ -53,6 +53,7 @@ class Controller(object): LOG.debug("Headers: %s", req.headers) # We need to create Service object and return its id params['tenant_id'] = tenant_id + params['datacenter_id'] = datacenter_id service_id = core_api.create_service(self.conf, params) return {'service': {'id': service_id}} diff --git a/windc/windc/core/__init__.py b/windc/windc/core/__init__.py index d65c689..1e6d1b8 100644 --- a/windc/windc/core/__init__.py +++ b/windc/windc/core/__init__.py @@ -14,3 +14,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +import builder_set + +builder_set.builders = builder_set.BuilderSet() +builder_set.builders.load() \ No newline at end of file diff --git a/windc/windc/core/api.py b/windc/windc/core/api.py index 65c209d..5c12a2b 100644 --- a/windc/windc/core/api.py +++ b/windc/windc/core/api.py @@ -15,32 +15,82 @@ # License for the specific language governing permissions and limitations # under the License. +from windc.db import api as db_api +from windc.core import change_events as events + def dc_get_index(conf, tenant_id): + dcs = db_api.datacenter_get_all(conf, tenant_id) + dc_list = [db_api.unpack_extra(dc) for dc in dcs] + return dc_list pass def create_dc(conf, params): + # We need to pack all attributes which are not defined by the model explicitly + dc_params = db_api.datacenter_pack_extra(params) + dc = db_api.datacenter_create(conf, dc_params) + event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_ADD) + events.change_event(conf, event, dc) + return dc.id pass -def delete_dc(conf, tenant_id, dc_id): +def delete_dc(conf, tenant_id, datacenter_id): + dc = db_api.datacenter_get(conf, tenant_id, datacenter_id) + event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_DELETE) + events.change_event(conf, event, dc) + db_api.datacenter_destroy(conf, datacenter_id) pass -def dc_get_data(conf, tenant_id, dc_id): +def dc_get_data(conf, tenant_id, datacenter_id): + dc = db_api.datacenter_get(conf, tenant_id, datacenter_id) + dc_data = db_api.unpack_extra(dc) + return dc_data pass -def update_dc(conf, tenant_id, dc_id, body): +def update_dc(conf, tenant_id, datacenter_id, body): + dc = db_api.datacenter_get(conf, tenant_id, datacenter_id) + old_dc = copy.deepcopy(dc) + db_api.pack_update(dc, body) + dc = db_api.datacenter_update(conf, datacenter_id, dc) + event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_MODIFY) + event.previous_state = old_dc + events.change_event(conf, event, dc) pass def service_get_index(conf, tenant_id, datacenter_id): + srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id, dtacenter_id) + srv_list = [db_api.unpack_extra(srv) for srv in srvcs] + return srv_list pass def create_service(conf, params): + # We need to pack all attributes which are not defined by the model explicitly + srv_params = db_api.service_pack_extra(params) + srv = db_api.service_create(conf, srv_params) + event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_ADD) + events.change_event(conf, event, srv) + return srv.id pass def delete_service(conf, tenant_id, datacenter_id, service_id): + srv = db_api.service_get(conf, service_id, tenant_id) + srv_data = db_api.unpack_extra(srv) + event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_DELETE) + events.change_event(conf, event, srv) + db_api.service_destroy(conf,service_id) pass def service_get_data(conf, tenant_id, datacenter_id, service_id): + srv = db_api.service_get(conf,service_id, tenant_id) + srv_data = db_api.unpack_extra(srv) + return srv_data pass def update_service(conf, tenant_id, datacenter_id, service_id, body): + srv = db_api.service_get(conf, service_id, tenant_id) + old_srv = copy.deepcopy(srv) + db_api.pack_update(srv, body) + srv = db_api.service_update(conf, service_id, srv) + event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_MODIFY) + event.previous_state = old_srv + events.change_event(conf, event, srv) pass \ No newline at end of file diff --git a/windc/windc/core/builder.py b/windc/windc/core/builder.py new file mode 100644 index 0000000..2dc68e8 --- /dev/null +++ b/windc/windc/core/builder.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +class Builder: + name = "Abstract Builder" + type = "abstract" + version = 0 + + def __init__(self): + pass + + def __str__(self): + return self.name+' type: '+self.type+ ' version: ' + str(self.version) + + def build(self, context, event, data): + pass + + + diff --git a/windc/windc/core/builder_set.py b/windc/windc/core/builder_set.py new file mode 100644 index 0000000..99d481c --- /dev/null +++ b/windc/windc/core/builder_set.py @@ -0,0 +1,72 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from builder import Builder + +import imp +import os +import sys, glob +import logging +import traceback + +LOG = logging.getLogger(__name__) +global builders + +def load_from_file(filepath): + class_inst = None + + mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1]) + + if file_ext.lower() == '.py': + py_mod = imp.load_source(mod_name, filepath) + + elif file_ext.lower() == '.pyc': + py_mod = imp.load_compiled(mod_name, filepath) + + if hasattr(py_mod, mod_name): + callable = getattr(__import__(mod_name),mod_name) + class_inst = callable() + + return class_inst + + +class BuilderSet: + def __init__(self): + self.path = './windc/core/builders' + sys.path.append(self.path) + self.set = {} + + def load(self): + + files = glob.glob(self.path+'/*.py') + + for file in files: + LOG.debug("Trying to load builder from file: %s", file) + try: + builder = load_from_file(file) + LOG.info("Buider '%s' loaded.", builder.name) + self.set[builder.type] = builder + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + LOG.error('Can`t load builder from the file %s. Skip it.', file) + LOG.debug(repr(traceback.format_exception(exc_type, exc_value, + exc_traceback))) + + + def reload(self): + self.set = {} + self.load() diff --git a/windc/windc/core/builders/ActiveDirectory.py b/windc/windc/core/builders/ActiveDirectory.py new file mode 100644 index 0000000..7181329 --- /dev/null +++ b/windc/windc/core/builders/ActiveDirectory.py @@ -0,0 +1,53 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging +LOG = logging.getLogger(__name__) + +from windc.core.builder import Builder +from windc.core import change_events as events +from windc.db import api as db_api + +class ActiveDirectory(Builder): + def __init__(self): + self.name = "Active Directory Builder" + self.type = "active_directory_service" + self.version = 1 + + def build(self, context, event, data): + dc = db_api.unpack_extra(data) + if event.scope == events.SCOPE_SERVICE_CHANGE: + LOG.info ("Got service change event. Analysing..") + if self.do_analysis(context, event, dc): + self.plan_changes(context, event, dc) + else: + LOG.debug("Not in my scope. Skip event.") + pass + + def do_analysis(self, context, event, data): + LOG.debug("Doing analysis for data: %s", data) + zones = data['zones'] + if data['type'] == self.type and len(zones) == 1: + LOG.debug("It is a service which I should build.") + return True + else: + return False + + def plan_changes(self, context, event, data): + pass + diff --git a/windc/windc/core/builders/DataCenter.py b/windc/windc/core/builders/DataCenter.py new file mode 100644 index 0000000..92bc1bf --- /dev/null +++ b/windc/windc/core/builders/DataCenter.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging +LOG = logging.getLogger(__name__) + +from windc.core.builder import Builder +from windc.core import change_events as events + +class DataCenter(Builder): + def __init__(self): + self.name = "Data Center Builder" + self.type = "datacenter" + self.version = 1 + + def build(self, context, event, data): + if event.scope == events.SCOPE_DATACENTER_CHANGE: + LOG.info ("Got Data Center change event. Analysing...") + else: + LOG.debug("Not in my scope. Skip event.") + pass + diff --git a/windc/windc/core/change_events.py b/windc/windc/core/change_events.py new file mode 100644 index 0000000..8324a7f --- /dev/null +++ b/windc/windc/core/change_events.py @@ -0,0 +1,53 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging +LOG = logging.getLogger(__name__) + +from windc.core import builder_set +#Declare events types + +SCOPE_SERVICE_CHANGE = "Service" +SCOPE_DATACENTER_CHANGE = "Datacenter" +SCOPE_VM_CHANGE = "VMChange" + +ACTION_ADD = "Add" +ACTION_MODIFY = "Modify" +ACTION_DELETE = "Delete" + +class Event: + scope = None + action = None + previous_state = None + def __init__(self, scope, action): + self.scope = scope + self.action = action + +def change_event(conf, event, data): + LOG.info("Change event of type: %s ", event) + context = {} + context['conf'] = conf + for builder_type in builder_set.builders.set: + builder = builder_set.builders.set[builder_type] + builder.build(context, event, data) + pass + + + + + diff --git a/windc/windc/db/api.py b/windc/windc/db/api.py index ada8815..d01c3ca 100644 --- a/windc/windc/db/api.py +++ b/windc/windc/db/api.py @@ -60,7 +60,7 @@ service_pack_extra = functools.partial(pack_extra, models.Service) # Datacenter -def datacenter_get(conf, datacenter_id, session=None): +def datacenter_get(conf, tenant_id, datacenter_id, session=None): session = session or get_session(conf) datacenter_ref = session.query(models.DataCenter).\ filter_by(id=datacenter_id).first() @@ -69,9 +69,10 @@ def datacenter_get(conf, datacenter_id, session=None): return datacenter_ref -def datacenter_get_all(conf): +def datacenter_get_all(conf, tenant_id): session = get_session(conf) - query = session.query(models.DataCenter) + query = session.query(models.DataCenter).\ + filter_by(tenant_id=tenant_id) return query.all() @@ -126,7 +127,7 @@ def service_get_all_by_vm_id(conf, tenant_id, vm_id): return query.all() -def service_get_all_by_datacenter_id(conf, datacenter_id): +def service_get_all_by_datacenter_id(conf, tenant_id, datacenter_id): session = get_session(conf) query = session.query(models.Service).filter_by(datacenter_id=datacenter_id) service_refs = query.all() diff --git a/windc/windc/db/migrate_repo/versions/001_Add_initial_tables.py b/windc/windc/db/migrate_repo/versions/001_Add_initial_tables.py index 9841db2..1200328 100644 --- a/windc/windc/db/migrate_repo/versions/001_Add_initial_tables.py +++ b/windc/windc/db/migrate_repo/versions/001_Add_initial_tables.py @@ -9,6 +9,7 @@ Table('datacenter', meta, Column('name', String(255)), Column('type', String(255)), Column('version', String(255)), + Column('tenant_id',String(100)), Column('KMS', String(80)), Column('WSUS', String(80)), Column('extra', Text()), diff --git a/windc/windc/db/models.py b/windc/windc/db/models.py index ae6c552..aa755ab 100644 --- a/windc/windc/db/models.py +++ b/windc/windc/db/models.py @@ -40,6 +40,7 @@ class DataCenter(DictBase, Base): name = Column(String(255)) type = Column(String(255)) version = Column(String(255)) + tenant_id = Column(String(100)) KMS = Column(String(80)) WSUS = Column(String(80)) extra = Column(JsonBlob()) diff --git a/windc/windc/drivers/__init__.py b/windc/windc/drivers/__init__.py new file mode 100644 index 0000000..e69de29