Skip to content

Commit

Permalink
Merge pull request #46 from BenjamenMeyer/enhancement_cinder-base
Browse files Browse the repository at this point in the history
Enhancement cinder base
  • Loading branch information
BenjamenMeyer committed Oct 9, 2017
2 parents b509769 + b61a108 commit 60a45d4
Show file tree
Hide file tree
Showing 18 changed files with 1,742 additions and 0 deletions.
538 changes: 538 additions & 0 deletions openstackinabox/manager.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions openstackinabox/models/cinder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
OpenStack Cinder Models
"""
from openstackinabox.models.cinder.model import CinderModel

__all__ = [
CinderModel
]
54 changes: 54 additions & 0 deletions openstackinabox/models/cinder/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sqlite3

import six

from openstackinabox.models import base_model


schema = [
'''
CREATE TABLE cinder_volume_types
(
typeid INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
extra_specs TEXT
)
'''
]


class CinderModel(base_model.BaseModel):

CHILD_MODELS = {
}

@staticmethod
def initialize_db_schema(db_instance):
dbcursor = db_instance.cursor()
for table_sql in schema:
dbcursor.execute(table_sql)
db_instance.commit()

@classmethod
def get_child_models(cls, instance, db_instance):
return {
model_name: model_type(instance, db_instance)
for model_name, model_type in six.iteritems(cls.CHILD_MODELS)
}

def __init__(self, keystone_model, initialize=True):
super(CinderModel, self).__init__('CinderModel')
self.keystone_model = keystone_model
self.database = sqlite3.connect(':memory:')
self.child_models = self.get_child_models(self, self.database)
if initialize:
self.init_database()

def init_database(self):
self.log_info('Initializing database')
self.initialize_db_schema(self.database)

# initialize the child models here

self.database.commit()
self.log_info('Database initialized')
8 changes: 8 additions & 0 deletions openstackinabox/services/cinder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
OpenStack Cinder Services
"""
from openstackinabox.services.cinder.v1 import CinderV1Service

__all__ = [
CinderV1Service
]
30 changes: 30 additions & 0 deletions openstackinabox/services/cinder/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import re

from openstackinabox.services.cinder.v1 import base
from openstackinabox.models.cinder import model
from openstackinabox.services.cinder.v1 import volumes


class CinderV1Service(base.CinderV1ServiceBase):

def __init__(self, keystone_service):
super(CinderV1Service, self).__init__(keystone_service, 'cinder/v1')
self.log_info('initializing cinder v1.0 services...')
self.model = model.CinderModel(keystone_service.model)
self.__subservices = [
{
'path': re.compile('^/volumes'),
'service': volumes.CinderV1Volumes(
self.model,
keystone_service
)
}
]

for subservice in self.__subservices:
self.register_subservice(
subservice['path'],
subservice['service']
)

self.log_info('initialized')
87 changes: 87 additions & 0 deletions openstackinabox/services/cinder/v1/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import uuid

from openstackinabox.models.cinder import model
from openstackinabox.services import base_service
from openstackinabox.services.keystone.v2 import exceptions


class CinderV1ServiceBase(base_service.BaseService):

@staticmethod
def make_volume_id():
return str(uuid.uuid4())

def __init__(self, keystone, *args, **kwargs):
super(CinderV1ServiceBase, self).__init__(*args, **kwargs)
self.__keystone = keystone
self.__model = None

@property
def model(self):
return self.__model

@model.setter
def model(self, value):
if isinstance(value, model.CinderModel):
self.__model = value
else:
raise TypeError('model is not an instance of CinderModel')

def helper_validate_token(self, request_headers,
enforce_admin, service_admin):
if 'x-auth-token' not in request_headers:
raise exceptions.KeystoneV2AuthForbiddenError('no auth token')

try:
auth_token = request_headers['x-auth-token']
user_data = None
self.log_debug('Service Admin Required: {0}'.format(service_admin))
self.log_debug('Enforce Admin Required: {0}'.format(enforce_admin))

if service_admin:
user_data = self.__keystone.model.validate_token_service_admin(
auth_token
)
elif enforce_admin:
user_data = self.__keystone.model.validate_token_admin(
auth_token
)
else:
user_data = self.__keystone.model.tokens.validate_token(
auth_token
)

self.log_debug(
'token {0} maps to tenant {1} and userid {2}'.format(
auth_token,
user_data['tenantid'],
user_data['userid']
)
)
except Exception as ex:
self.log_exception('invalid or expired auth token')
raise exceptions.KeystoneV2AuthUnauthorizedError(
'invalid or expired auth token'
)

return user_data

def helper_authenticate(self, request_headers, headers,
enforce_admin, service_admin):

try:
user_data = self.helper_validate_token(
request_headers,
enforce_admin,
service_admin
)

except exceptions.KeystoneV2AuthForbiddenError:
self.log_exception('no token')
return (403, headers, 'Forbidden')

except exceptions.KeystoneV2AuthUnauthorizedError:
self.log_exception('invalid or expired token')
return (401, headers, 'Not Authorized')

return user_data
157 changes: 157 additions & 0 deletions openstackinabox/services/cinder/v1/volumes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import json
import re

from openstackinabox.services.cinder.v1 import base


class CinderV1Volumes(base.CinderV1ServiceBase):

# Note: OpenStackInABox's Keystone Service doesn't have support
# right now for inserting the tenant-id into the URL when
# generating the service catalog. So the service URL here
# can't search for it
ALL_VOLUMES = re.compile('^/volumes$')
ALL_VOLUMES_DETAILED = re.compile('^/volumes/detail$')
SPECIFIC_VOLUME = re.compile('^/volumes/[\w-]+$')

def __init__(self, model, keystone_service):
super(CinderV1Volumes, self).__init__(
keystone_service,
'cinder/v1/volumes'
)
self.model = model

self.__handlers = [
{
'verb': base.CinderV1ServiceBase.POST,
'path': self.ALL_VOLUMES,
'handler': CinderV1Volumes.handle_create_volume
},
{
'verb': base.CinderV1ServiceBase.GET,
'path': self.ALL_VOLUMES,
'handler': CinderV1Volumes.handle_retrieve_volumes
},
{
'verb': base.CinderV1ServiceBase.PUT,
'path': self.SPECIFIC_VOLUME,
'handler': CinderV1Volumes.handle_update_volume
},
{
'verb': base.CinderV1ServiceBase.DELETE,
'path': self.SPECIFIC_VOLUME,
'handler': CinderV1Volumes.handle_delete_volume
},
{
'verb': base.CinderV1ServiceBase.GET,
'path': self.SPECIFIC_VOLUME,
'handler': CinderV1Volumes.get_subroute
# NOTE: There is a conflict between SPECIFIC_VOLUME and
# ALL_VOLUMES when it comes to the GET verb. Therefore
# a special sub-router is required to propery direct
# the request as StackInABox doesn't allow two registrations
# on the same VERB where the path may match.
}
]

for handler in self.__handlers:
self.register(
handler['verb'],
handler['path'],
handler['handler']
)

def get_subroute(self, request, uri, headers):
uri_parts = uri.split('/')
if uri_parts[-1] == 'detail':
return self.handle_retrieve_volumes_detailed(
request, uri, headers
)
else:
return self.handle_retrieve_volume_details(
request, uri, headers
)

def handle_create_volume(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#create-a-volume
return (500, headers, 'Not yet implemented')

def handle_retrieve_volumes(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#retrieve-volumes
return (500, headers, 'Not yet implemented')

def handle_retrieve_volumes_detailed(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#retrieve-volumes-detailed
return (500, headers, 'Not yet implemented')

def handle_update_volume(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#update-a-volume
return (500, headers, 'Not yet implemented')

def handle_delete_volume(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#delete-a-volume
return (500, headers, 'Not yet implemented')

def handle_retrieve_volume_details(self, request, uri, headers):
# https://developer.rackspace.com/docs/cloud-block-storage/v1/
# api-reference/cbs-volumes-operations/#retrieve-details-for-a-volume
req_headers = request.headers
self.log_request(uri, request)

# Validate the token in the request headers
# - if it's invalid for some reason a tuple is returned
# - if all is good, then a dict with the user information is returned
user_data = self.helper_authenticate(
req_headers, headers, False, False
)

# user_data will be a tuple in the case of 401/403 errors
if isinstance(user_data, tuple):
return user_data

# volume id in the URI, nothing in the body
result = self.SPECIFIC_VOLUME.match(uri)
if result and not uri.split('/')[-1].startswith('_'):
volume_id = result.group(0)

# TODO: Mapping Tenant-ID in URL per OpenStack API norms
# OpenStackInABox Keystone Service doesn't support the insert
# of the tenant-id into the URL so the URL can't be used to
# validate the tenant-id of the request.
#
# tenant_id = result.group(0)
# volume_id = result.group(1)
# if tenant_id != user_data['tenantid']:
# return (400, headers, 'Invalid client request')

# technically the data should be looked up in the CinderModel
# and a result returned accordingly; but right now the goal
# is a very specific test so for MVP just return the result
response_body = {
'volume': {
'attachments': [],
'availability_zone': 'nova',
'bootable': 'false',
'created_at': '',
'display_description': 'clone in error state',
'display_name': 'clone_test',
'id': volume_id,
'image_id': None,
'metadata': {},
'size': 100,
'snapshot_id': None,
'source_volid': volume_id, # self-referential
'status': 'error',
'volume_type': 'SATA',
}
}

return (200, headers, json.dumps(response_body))

else:
return (400, headers, 'Invalid client request')
Empty file.

0 comments on commit 60a45d4

Please sign in to comment.