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.
This commit is contained in:
Georgy Okrokvertskhov 2013-02-12 15:05:27 -08:00
parent 8994ffcab1
commit c138dd8f40
21 changed files with 367 additions and 67 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
{
"name": "Test Data Center 2",
"type": "SingleZone",
"version":"1.1",
"KMS":"172.16.1.2",
"WSUS":"172.16.1.3"
}

View File

@ -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

View File

@ -0,0 +1,8 @@
{
"type": "active_directory_service",
"zones": ["zone1"],
"domain": "ACME.cloud",
"AdminUser": "Admin",
"AdminPassword": "StrongPassword",
"DomainControllerNames": ["APP-AD001","APP-AD002"]
}

View File

@ -0,0 +1 @@
curl -X GET http://localhost:8082/foo/datacenters

View File

@ -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)

View File

@ -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}}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()),

View File

@ -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())

View File