Initial commit to use Pecan for RestAPI service

Change-Id: Ie1fd9dbec8a5163a2959846c6ca845572ce23b12
This commit is contained in:
Yichen Wang 2015-06-30 14:33:08 -07:00
parent ae03ef1d35
commit 6d2152767a
13 changed files with 404 additions and 72 deletions

View File

@ -13,6 +13,7 @@ lxml>=3.4.0
oslo.log>=1.0.0
oslo.utils>=1.2.0
paramiko>=1.14.0
pecan>=0.9.0
pycrypto>=2.6.1
pymongo>=2.7.2
python-glanceclient>=0.15.0

142
scale/kb_config.py Normal file
View File

@ -0,0 +1,142 @@
# Copyright 2015 Cisco Systems, Inc. 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 os
import sys
import configure
import log as logging
from oslo_config import cfg
import credentials
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
# Some hardcoded client side options we do not want users to change
hardcoded_client_cfg = {
# Number of tenants to be created on the cloud
'number_tenants': 1,
# Number of Users to be created inside the tenant
'users_per_tenant': 1,
# Number of routers to be created within the context of each User
# For now support only 1 router per user
'routers_per_user': 1,
# Number of networks to be created within the context of each Router
# Assumes 1 subnet per network
'networks_per_router': 1,
# Number of VM instances to be created within the context of each Network
'vms_per_network': 1,
# Number of security groups per network
'secgroups_per_network': 1
}
def get_absolute_path_for_file(file_name):
'''
Return the filename in absolute path for any file
passed as relateive path.
'''
if os.path.isabs(__file__):
abs_file_path = os.path.join(__file__.split("kb_config.py")[0],
file_name)
else:
abs_file = os.path.abspath(__file__)
abs_file_path = os.path.join(abs_file.split("kb_config.py")[0],
file_name)
return abs_file_path
class KBConfig(object):
def __init__(self):
# The default configuration file for KloudBuster
default_cfg_file = get_absolute_path_for_file("cfg.scale.yaml")
# Read the configuration file
self.config_scale = configure.Configuration.from_file(default_cfg_file).configure()
self.cred_tested = None
self.cred_testing = None
self.server_cfg = None
self.client_cfg = None
self.topo_cfg = None
def init_with_cli(self):
self.get_credentials()
self.get_configs()
self.get_topo_cfg()
def init_with_rest_api(self, **kwargs):
self.cred_tested = kwargs['cred_tested']
self.cred_testing = kwargs['cred_testing']
self.server_cfg = kwargs['server_cfg']
self.client_cfg = kwargs['client_cfg']
self.topo_cfg = kwargs['topo_cfg']
def get_total_vm_count(self, config):
return (config['number_tenants'] * config['users_per_tenant'] *
config['routers_per_user'] * config['networks_per_router'] *
config['vms_per_network'])
def get_credentials(self):
# Retrieve the credentials
self.cred_tested = credentials.Credentials(CONF.tested_rc, CONF.passwd_tested, CONF.no_env)
if CONF.testing_rc and CONF.testing_rc != CONF.tested_rc:
self.cred_testing = credentials.Credentials(CONF.testing_rc,
CONF.passwd_testing,
CONF.no_env)
else:
# Use the same openrc file for both cases
self.cred_testing = self.cred_tested
def get_configs(self):
if CONF.config:
alt_config = configure.Configuration.from_file(CONF.config).configure()
self.config_scale = self.config_scale.merge(alt_config)
# Initialize the key pair name
if self.config_scale['public_key_file']:
# verify the public key file exists
if not os.path.exists(self.config_scale['public_key_file']):
LOG.error('Error: Invalid public key file: ' + self.config_scale['public_key_file'])
sys.exit(1)
else:
# pick the user's public key if there is one
pub_key = os.path.expanduser('~/.ssh/id_rsa.pub')
if os.path.isfile(pub_key):
self.config_scale['public_key_file'] = pub_key
LOG.info('Using %s as public key for all VMs' % (pub_key))
# A bit of config dict surgery, extract out the client and server side
# and transplant the remaining (common part) into the client and server dict
self.server_cfg = self.config_scale.pop('server')
self.client_cfg = self.config_scale.pop('client')
self.server_cfg.update(self.config_scale)
self.client_cfg.update(self.config_scale)
# Hardcode a few client side options
self.client_cfg.update(hardcoded_client_cfg)
# Adjust the VMs per network on the client side to match the total
# VMs on the server side (1:1)
# There is an additional VM in client kloud as a proxy node
self.client_cfg['vms_per_network'] =\
self.get_total_vm_count(self.server_cfg) + 1
def get_topo_cfg(self):
if CONF.topology:
self.topo_cfg = configure.Configuration.from_file(CONF.topology).configure()

View File

@ -0,0 +1 @@
recursive-include public *

68
scale/kb_server/config.py Normal file
View File

@ -0,0 +1,68 @@
# Copyright 2015 Cisco Systems, Inc. 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.
# Server Specific Configurations
server = {
'port': '8080',
'host': '0.0.0.0'
}
# Pecan Application Configurations
app = {
'root': 'kb_server.controllers.root.RootController',
'modules': ['kb_server'],
'static_root': '%(confdir)s/public',
'template_path': '%(confdir)s/kb_server/templates',
'debug': True,
'errors': {
404: '/error/404',
'__force_dict__': True
}
}
logging = {
'root': {'level': 'INFO', 'handlers': ['console']},
'loggers': {
'kb_server': {'level': 'DEBUG', 'handlers': ['console']},
'pecan': {'level': 'DEBUG', 'handlers': ['console']},
'py.warnings': {'handlers': ['console']},
'__force_dict__': True
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'color'
}
},
'formatters': {
'simple': {
'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
'[%(threadName)s] %(message)s')
},
'color': {
'()': 'pecan.log.ColorFormatter',
'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
'[%(threadName)s] %(message)s'),
'__force_dict__': True
}
}
}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

View File

@ -0,0 +1,27 @@
# Copyright 2015 Cisco Systems, Inc. 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 kb_server import model
from pecan import make_app
def setup_app(config):
model.init_model()
app_conf = dict(config.app)
return make_app(
app_conf.pop('root'),
logging=getattr(config, 'logging', {}),
**app_conf
)

View File

@ -0,0 +1,46 @@
# Copyright 2015 Cisco Systems, Inc. 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 os
import sys
kb_main_path = os.path.split(os.path.abspath(__file__))[0] + "/../../.."
sys.path.append(kb_main_path)
from kb_config import KBConfig
from pecan import expose
class ConfigController(object):
def __init__(self):
self.kb_config = KBConfig()
self.cur_config = None
@expose(generic=True)
def default_config(self):
def_config = self.kb_config.config_scale
return str(def_config)
@expose(generic=True)
def current_config(self):
return str(self.cur_config)
@expose(generic=True)
def status(self):
return "RETURN CURRENT STATUS HERE"
@status.when(method='PUT')
def status_PUT(self, **kw):
# @TODO(recursively update the config dictionary with the information
# provided by application (client))
return str(kw)

View File

@ -0,0 +1,33 @@
# Copyright 2015 Cisco Systems, Inc. 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 config import ConfigController
from pecan import abort
from pecan import expose
class APIController(object):
@expose()
def _lookup(self, primary_key, *remainder):
if primary_key == "config":
return ConfigController(), remainder
else:
abort(404)
class RootController(object):
@expose()
def _lookup(self, primary_key, *remainder):
if primary_key == "api":
return APIController(), remainder
else:
abort(404)

View File

@ -0,0 +1,29 @@
# Copyright 2015 Cisco Systems, Inc. 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 pecan import conf # noqa
def init_model():
"""
This is a stub method which is called at application startup time.
If you need to bind to a parsed database configuration, set up tables or
ORM classes, or perform any database initialization, this is the
recommended place to do it.
For more information working with databases, and some common recipes,
see http://pecan.readthedocs.org/en/latest/databases.html
"""
pass

View File

@ -0,0 +1,6 @@
[nosetests]
match=^test
where=kb_server
nocapture=1
cover-package=kb_server
cover-erase=1

38
scale/kb_server/setup.py Normal file
View File

@ -0,0 +1,38 @@
# Copyright 2015 Cisco Systems, Inc. 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.
# -*- coding: utf-8 -*-
try:
from setuptools import find_packages
from setuptools import setup
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import find_packages
from setuptools import setup
setup(
name='kb_server',
version='0.1',
description='',
author='',
author_email='',
install_requires=[
"pecan",
],
test_suite='kb_server',
zip_safe=False,
include_package_data=True,
packages=find_packages(exclude=['ez_setup'])
)

View File

@ -21,8 +21,8 @@ import traceback
import base_compute
import base_network
import configure
from glanceclient.v2 import client as glanceclient
from kb_config import KBConfig
from kb_runner import KBRunner
from kb_scheduler import KBScheduler
from keystoneclient.v2_0 import client as keystoneclient
@ -32,28 +32,12 @@ from oslo_config import cfg
from tabulate import tabulate
import tenant
import credentials
import sshutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def get_absolute_path_for_file(file_name):
'''
Return the filename in absolute path for any file
passed as relateive path.
'''
if os.path.isabs(__file__):
abs_file_path = os.path.join(__file__.split("kloudbuster.py")[0],
file_name)
else:
abs_file = os.path.abspath(__file__)
abs_file_path = os.path.join(abs_file.split("kloudbuster.py")[0],
file_name)
return abs_file_path
def create_keystone_client(admin_creds):
"""
Return the keystone client and auth URL given a credential
@ -404,8 +388,6 @@ hardcoded_client_cfg = {
if __name__ == '__main__':
# The default configuration file for KloudBuster
default_cfg_file = get_absolute_path_for_file("cfg.scale.yaml")
cli_opts = [
cfg.StrOpt("config",
@ -441,64 +423,23 @@ if __name__ == '__main__':
logging.setup("kloudbuster")
# Read the configuration file
config_scale = configure.Configuration.from_file(default_cfg_file).configure()
if CONF.config:
alt_config = configure.Configuration.from_file(CONF.config).configure()
config_scale = config_scale.merge(alt_config)
kb_config = KBConfig()
kb_config.init_with_cli()
if CONF.topology:
topology = configure.Configuration.from_file(CONF.topology).configure()
else:
topology = None
# Retrieve the credentials
cred = credentials.Credentials(CONF.tested_rc, CONF.passwd_tested, CONF.no_env)
if CONF.testing_rc and CONF.testing_rc != CONF.tested_rc:
cred_testing = credentials.Credentials(CONF.testing_rc,
CONF.passwd_testing,
CONF.no_env)
else:
# Use the same openrc file for both cases
cred_testing = cred
# Initialize the key pair name
if config_scale['public_key_file']:
# verify the public key file exists
if not os.path.exists(config_scale['public_key_file']):
LOG.error('Error: Invalid public key file: ' + config_scale['public_key_file'])
sys.exit(1)
else:
# pick the user's public key if there is one
pub_key = os.path.expanduser('~/.ssh/id_rsa.pub')
if os.path.isfile(pub_key):
config_scale['public_key_file'] = pub_key
LOG.info('Using %s as public key for all VMs' % (pub_key))
# A bit of config dict surgery, extract out the client and server side
# and transplant the remaining (common part) into the client and server dict
server_side_cfg = config_scale.pop('server')
client_side_cfg = config_scale.pop('client')
server_side_cfg.update(config_scale)
client_side_cfg.update(config_scale)
# Hardcode a few client side options
client_side_cfg.update(hardcoded_client_cfg)
# Adjust the VMs per network on the client side to match the total
# VMs on the server side (1:1)
# There is an additional VM in client kloud as a proxy node
client_side_cfg['vms_per_network'] = get_total_vm_count(server_side_cfg) + 1
image_check = check_and_upload_images(cred, cred_testing, server_side_cfg.image_name,
client_side_cfg.image_name)
image_check = check_and_upload_images(
kb_config.cred_tested,
kb_config.cred_testing,
kb_config.server_cfg.image_name,
kb_config.client_cfg.image_name)
if not image_check:
sys.exit(1)
# The KloudBuster class is just a wrapper class
# levarages tenant and user class for resource creations and
# deletion
kloudbuster = KloudBuster(cred, cred_testing, server_side_cfg, client_side_cfg, topology)
# levarages tenant and user class for resource creations and deletion
kloudbuster = KloudBuster(
kb_config.cred_tested, kb_config.cred_testing,
kb_config.server_cfg, kb_config.client_cfg,
kb_config.topo_cfg)
kloudbuster.run()
if CONF.json: