-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from BenjamenMeyer/enhancement_cinder-base
Enhancement cinder base
- Loading branch information
Showing
18 changed files
with
1,742 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.