Modify api to support edition of closed entity
This commit is contained in:
parent
47ab4568ce
commit
a81c0554b5
12
Dockerfile
12
Dockerfile
@ -7,11 +7,15 @@ ADD README.md /opt/almanach/src/
|
|||||||
ADD requirements.txt /opt/almanach/src/
|
ADD requirements.txt /opt/almanach/src/
|
||||||
ADD LICENSE /opt/almanach/src/
|
ADD LICENSE /opt/almanach/src/
|
||||||
ADD almanach/resources/config/almanach.cfg /etc/almanach.cfg
|
ADD almanach/resources/config/almanach.cfg /etc/almanach.cfg
|
||||||
|
COPY docker-entrypoint.sh /opt/almanach/entrypoint.sh
|
||||||
|
|
||||||
WORKDIR /opt/almanach
|
RUN cd /opt/almanach/src && \
|
||||||
|
|
||||||
RUN cd src && \
|
|
||||||
pip install -r requirements.txt && \
|
pip install -r requirements.txt && \
|
||||||
PBR_VERSION=2.0.dev0 python setup.py install
|
PBR_VERSION=2.0.dev0 python setup.py install && \
|
||||||
|
chmod +x /opt/almanach/entrypoint.sh
|
||||||
|
|
||||||
|
VOLUME /opt/almanach
|
||||||
|
|
||||||
USER nobody
|
USER nobody
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/almanach/entrypoint.sh"]
|
||||||
|
@ -68,8 +68,8 @@ Running Almanach with Docker
|
|||||||
The actual Docker configuration assume that you already have RabbitMQ (mandatory for Openstack) and MongoDB configured for Almanach.
|
The actual Docker configuration assume that you already have RabbitMQ (mandatory for Openstack) and MongoDB configured for Almanach.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export RABBITMQ_URL="amqp://openstack:openstack@my-hostname:5672/"
|
export RABBITMQ_URL="amqp://guest:guest@messaging:5672/"
|
||||||
export MONGODB_URL="mongodb://almanach:almanach@my-hostname:27017/almanach"
|
export MONGODB_URL="mongodb://almanach:almanach@database:27017/almanach"
|
||||||
|
|
||||||
docker-compose build
|
docker-compose build
|
||||||
docker-compose up
|
docker-compose up
|
||||||
|
@ -14,16 +14,20 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import jsonpickle
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from almanach.common.validation_exception import InvalidAttributeException
|
|
||||||
|
import jsonpickle
|
||||||
|
|
||||||
from flask import Blueprint, Response, request
|
from flask import Blueprint, Response, request
|
||||||
|
|
||||||
from werkzeug.wrappers import BaseResponse
|
from werkzeug.wrappers import BaseResponse
|
||||||
|
|
||||||
|
from almanach.common.exceptions.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||||
|
from almanach.common.exceptions.multiple_entities_matching_query import MultipleEntitiesMatchingQuery
|
||||||
|
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||||
from almanach import config
|
from almanach import config
|
||||||
from almanach.common.date_format_exception import DateFormatException
|
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||||
|
|
||||||
api = Blueprint("api", __name__)
|
api = Blueprint("api", __name__)
|
||||||
controller = None
|
controller = None
|
||||||
@ -53,9 +57,18 @@ def to_json(api_call):
|
|||||||
except InvalidAttributeException as e:
|
except InvalidAttributeException as e:
|
||||||
logging.warning(e.get_error_message())
|
logging.warning(e.get_error_message())
|
||||||
return encode({"error": e.get_error_message()}), 400, {"Content-Type": "application/json"}
|
return encode({"error": e.get_error_message()}), 400, {"Content-Type": "application/json"}
|
||||||
|
except MultipleEntitiesMatchingQuery as e:
|
||||||
|
logging.warning(e.message)
|
||||||
|
return encode({"error": "Multiple entities found while updating closed"}), 400, {
|
||||||
|
"Content-Type": "application/json"}
|
||||||
|
except AlmanachEntityNotFoundException as e:
|
||||||
|
logging.warning(e.message)
|
||||||
|
return encode({"error": "Entity not found for updating closed"}), 400, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
return Response(encode({"error": e.message}), 500, {"Content-Type": "application/json"})
|
return Response(encode({"error": e.message}), 500, {"Content-Type": "application/json"})
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@ -256,7 +269,12 @@ def list_entity(project_id):
|
|||||||
def update_instance_entity(instance_id):
|
def update_instance_entity(instance_id):
|
||||||
data = json.loads(request.data)
|
data = json.loads(request.data)
|
||||||
logging.info("Updating instance entity with id %s with data %s", instance_id, data)
|
logging.info("Updating instance entity with id %s with data %s", instance_id, data)
|
||||||
return controller.update_active_instance_entity(instance_id=instance_id, **data)
|
if 'start' in request.args:
|
||||||
|
start, end = get_period()
|
||||||
|
result = controller.update_inactive_entity(instance_id=instance_id, start=start, end=end, **data)
|
||||||
|
else:
|
||||||
|
result = controller.update_active_instance_entity(instance_id=instance_id, **data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@api.route("/volume_types", methods=["GET"])
|
@api.route("/volume_types", methods=["GET"])
|
||||||
|
@ -13,15 +13,16 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import pymongo
|
|
||||||
|
|
||||||
|
import pymongo
|
||||||
from pymongo.errors import ConfigurationError
|
from pymongo.errors import ConfigurationError
|
||||||
from almanach import config
|
|
||||||
from almanach.common.almanach_exception import AlmanachException
|
|
||||||
from almanach.common.volume_type_not_found_exception import VolumeTypeNotFoundException
|
|
||||||
from almanach.core.model import build_entity_from_dict, VolumeType
|
|
||||||
from pymongomodem.utils import decode_output, encode_input
|
from pymongomodem.utils import decode_output, encode_input
|
||||||
|
|
||||||
|
from almanach import config
|
||||||
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
from almanach.common.exceptions.volume_type_not_found_exception import VolumeTypeNotFoundException
|
||||||
|
from almanach.core.model import build_entity_from_dict, VolumeType
|
||||||
|
|
||||||
|
|
||||||
def database(function):
|
def database(function):
|
||||||
def _connection(self, *args, **kwargs):
|
def _connection(self, *args, **kwargs):
|
||||||
@ -55,7 +56,6 @@ def ensureindex(db):
|
|||||||
|
|
||||||
|
|
||||||
class DatabaseAdapter(object):
|
class DatabaseAdapter(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.db = None
|
self.db = None
|
||||||
|
|
||||||
@ -90,6 +90,22 @@ class DatabaseAdapter(object):
|
|||||||
entities = self._get_entities_from_db(args)
|
entities = self._get_entities_from_db(args)
|
||||||
return [build_entity_from_dict(entity) for entity in entities]
|
return [build_entity_from_dict(entity) for entity in entities]
|
||||||
|
|
||||||
|
@database
|
||||||
|
def list_entities_by_id(self, entity_id, start, end):
|
||||||
|
entities = self.db.entity.find({"entity_id": entity_id,
|
||||||
|
"start": {"$gte": start},
|
||||||
|
"$and": [
|
||||||
|
{"end": {"$ne": None}},
|
||||||
|
{"end": {"$lte": end}}
|
||||||
|
]
|
||||||
|
}, {"_id": 0})
|
||||||
|
return [build_entity_from_dict(entity) for entity in entities]
|
||||||
|
|
||||||
|
@database
|
||||||
|
def update_closed_entity(self, entity, data):
|
||||||
|
self.db.entity.update({"entity_id": entity.entity_id, "start": entity.start, "end": entity.end},
|
||||||
|
{"$set": data})
|
||||||
|
|
||||||
@database
|
@database
|
||||||
def insert_entity(self, entity):
|
def insert_entity(self, entity):
|
||||||
self._insert_entity(entity.as_dict())
|
self._insert_entity(entity.as_dict())
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
class AlmanachEntityNotFoundException(Exception):
|
|
||||||
pass
|
|
0
almanach/common/exceptions/__init__.py
Normal file
0
almanach/common/exceptions/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright 2016 Internap.
|
||||||
|
#
|
||||||
|
# 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 almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
|
|
||||||
|
class AlmanachEntityNotFoundException(AlmanachException):
|
||||||
|
pass
|
@ -11,9 +11,10 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
|
|
||||||
class DateFormatException(Exception):
|
class DateFormatException(AlmanachException):
|
||||||
|
|
||||||
def __init__(self, message=None):
|
def __init__(self, message=None):
|
||||||
if not message:
|
if not message:
|
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright 2016 Internap.
|
||||||
|
#
|
||||||
|
# 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 almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleEntitiesMatchingQuery(AlmanachException):
|
||||||
|
pass
|
26
almanach/common/exceptions/validation_exception.py
Normal file
26
almanach/common/exceptions/validation_exception.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright 2016 Internap.
|
||||||
|
#
|
||||||
|
# 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 almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAttributeException(AlmanachException):
|
||||||
|
def __init__(self, errors):
|
||||||
|
self.errors = errors
|
||||||
|
|
||||||
|
def get_error_message(self):
|
||||||
|
messages = {}
|
||||||
|
for error in self.errors:
|
||||||
|
messages[error.path[0]] = error.msg
|
||||||
|
|
||||||
|
return messages
|
@ -11,9 +11,10 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
|
|
||||||
class VolumeTypeNotFoundException(Exception):
|
class VolumeTypeNotFoundException(AlmanachException):
|
||||||
|
|
||||||
def __init__(self, volume_type_id, message=None):
|
def __init__(self, volume_type_id, message=None):
|
||||||
if not message:
|
if not message:
|
@ -1,10 +0,0 @@
|
|||||||
class InvalidAttributeException(Exception):
|
|
||||||
def __init__(self, errors):
|
|
||||||
self.errors = errors
|
|
||||||
|
|
||||||
def get_error_message(self):
|
|
||||||
messages = {}
|
|
||||||
for error in self.errors:
|
|
||||||
messages[error.path[0]] = error.msg
|
|
||||||
|
|
||||||
return messages
|
|
@ -16,7 +16,7 @@ import ConfigParser
|
|||||||
import os
|
import os
|
||||||
import os.path as os_path
|
import os.path as os_path
|
||||||
|
|
||||||
from almanach.common.almanach_exception import AlmanachException
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
|
|
||||||
configuration = ConfigParser.RawConfigParser()
|
configuration = ConfigParser.RawConfigParser()
|
||||||
|
|
||||||
|
@ -13,14 +13,16 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import pytz
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import pytz
|
||||||
from dateutil import parser as date_parser
|
from dateutil import parser as date_parser
|
||||||
|
|
||||||
from pkg_resources import get_distribution
|
from pkg_resources import get_distribution
|
||||||
|
|
||||||
from almanach.common.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
from almanach.common.exceptions.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||||
from almanach.common.date_format_exception import DateFormatException
|
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||||
|
from almanach.common.exceptions.multiple_entities_matching_query import MultipleEntitiesMatchingQuery
|
||||||
from almanach.core.model import Instance, Volume, VolumeType
|
from almanach.core.model import Instance, Volume, VolumeType
|
||||||
from almanach.validators.instance_validator import InstanceValidator
|
from almanach.validators.instance_validator import InstanceValidator
|
||||||
from almanach import config
|
from almanach import config
|
||||||
@ -107,6 +109,19 @@ class Controller(object):
|
|||||||
instance.last_event = rebuild_date
|
instance.last_event = rebuild_date
|
||||||
self.database_adapter.insert_entity(instance)
|
self.database_adapter.insert_entity(instance)
|
||||||
|
|
||||||
|
def update_inactive_entity(self, instance_id, start, end, **kwargs):
|
||||||
|
inactive_entities = self.database_adapter.list_entities_by_id(instance_id, start, end)
|
||||||
|
if len(inactive_entities) > 1:
|
||||||
|
raise MultipleEntitiesMatchingQuery()
|
||||||
|
if len(inactive_entities) < 1:
|
||||||
|
raise AlmanachEntityNotFoundException("InstanceId: {0} Not Found with start".format(instance_id))
|
||||||
|
entity = inactive_entities[0]
|
||||||
|
entity_update = self._transform_attribute_to_match_entity_attribute(**kwargs)
|
||||||
|
self.database_adapter.update_closed_entity(entity=entity, data=entity_update)
|
||||||
|
start = entity_update.get('start') or start
|
||||||
|
end = entity_update.get('end') or end
|
||||||
|
return self.database_adapter.list_entities_by_id(instance_id, start, end)[0]
|
||||||
|
|
||||||
def update_active_instance_entity(self, instance_id, **kwargs):
|
def update_active_instance_entity(self, instance_id, **kwargs):
|
||||||
try:
|
try:
|
||||||
InstanceValidator().validate_update(kwargs)
|
InstanceValidator().validate_update(kwargs)
|
||||||
@ -155,18 +170,20 @@ class Controller(object):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _update_instance_object(self, instance, **kwargs):
|
def _update_instance_object(self, instance, **kwargs):
|
||||||
for attribute, key in dict(start="start_date", end="end_date").items():
|
for key, value in self._transform_attribute_to_match_entity_attribute(**kwargs).items():
|
||||||
value = kwargs.get(key)
|
setattr(instance, key, value)
|
||||||
if value:
|
|
||||||
setattr(instance, attribute, self._validate_and_parse_date(value))
|
|
||||||
logging.info("Updating entity for instance '{0}' with {1}={2}".format(instance.entity_id, key, value))
|
logging.info("Updating entity for instance '{0}' with {1}={2}".format(instance.entity_id, key, value))
|
||||||
|
|
||||||
|
def _transform_attribute_to_match_entity_attribute(self, **kwargs):
|
||||||
|
entity = {}
|
||||||
|
for attribute, key in dict(start="start_date", end="end_date").items():
|
||||||
|
if kwargs.get(key):
|
||||||
|
entity[attribute] = self._validate_and_parse_date(kwargs.get(key))
|
||||||
|
|
||||||
for attribute in ["name", "flavor", "os", "metadata"]:
|
for attribute in ["name", "flavor", "os", "metadata"]:
|
||||||
value = kwargs.get(attribute)
|
if kwargs.get(attribute):
|
||||||
if value:
|
entity[attribute] = kwargs.get(attribute)
|
||||||
setattr(instance, attribute, value)
|
return entity
|
||||||
logging.info(
|
|
||||||
"Updating entity for instance '{0}' with {1}={2}".format(instance.entity_id, attribute, value))
|
|
||||||
|
|
||||||
def _volume_attach_instance(self, volume_id, date, attachments):
|
def _volume_attach_instance(self, volume_id, date, attachments):
|
||||||
volume = self.database_adapter.get_active_entity(volume_id)
|
volume = self.database_adapter.get_active_entity(volume_id)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from almanach.common.validation_exception import InvalidAttributeException
|
|
||||||
from voluptuous import Schema, MultipleInvalid, Datetime, Required
|
from voluptuous import Schema, MultipleInvalid, Datetime, Required
|
||||||
|
|
||||||
|
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||||
|
|
||||||
|
|
||||||
class InstanceValidator(object):
|
class InstanceValidator(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -4,7 +4,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
command: almanach api /etc/almanach.cfg --host 0.0.0.0
|
command: api
|
||||||
environment:
|
environment:
|
||||||
MONGODB_URL: ${MONGODB_URL}
|
MONGODB_URL: ${MONGODB_URL}
|
||||||
ports:
|
ports:
|
||||||
@ -13,7 +13,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
command: almanach collector /etc/almanach.cfg
|
command: collector
|
||||||
environment:
|
environment:
|
||||||
MONGODB_URL: ${MONGODB_URL}
|
MONGODB_URL: ${MONGODB_URL}
|
||||||
RABBITMQ_URL: ${RABBITMQ_URL}
|
RABBITMQ_URL: ${RABBITMQ_URL}
|
||||||
|
13
docker-entrypoint.sh
Executable file
13
docker-entrypoint.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Entering the entrypoint"
|
||||||
|
if [ "$1" = 'api' ]; then
|
||||||
|
echo "Starting the api"
|
||||||
|
almanach api /etc/almanach.cfg --host 0.0.0.0
|
||||||
|
elif [ "$1" = 'collector' ]; then
|
||||||
|
echo "Starting the collector"
|
||||||
|
almanach collector /etc/almanach.cfg
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
@ -13,12 +13,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import pytz
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pytz
|
||||||
from flexmock import flexmock, flexmock_teardown
|
from flexmock import flexmock, flexmock_teardown
|
||||||
|
|
||||||
from almanach.common.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
from almanach.common.exceptions.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||||
from tests import messages
|
from tests import messages
|
||||||
from almanach.adapters.bus_adapter import BusAdapter
|
from almanach.adapters.bus_adapter import BusAdapter
|
||||||
|
|
||||||
|
@ -12,18 +12,20 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
import unittest
|
import unittest
|
||||||
import mongomock
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
import mongomock
|
||||||
from flexmock import flexmock, flexmock_teardown
|
from flexmock import flexmock, flexmock_teardown
|
||||||
from hamcrest import assert_that, contains_inanyorder
|
from hamcrest import assert_that, contains_inanyorder
|
||||||
|
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
import pytz
|
||||||
|
|
||||||
from almanach.adapters.database_adapter import DatabaseAdapter
|
from almanach.adapters.database_adapter import DatabaseAdapter
|
||||||
from almanach.common.volume_type_not_found_exception import VolumeTypeNotFoundException
|
from almanach.common.exceptions.volume_type_not_found_exception import VolumeTypeNotFoundException
|
||||||
from almanach.common.almanach_exception import AlmanachException
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
from almanach import config
|
from almanach import config
|
||||||
from almanach.core.model import todict
|
from almanach.core.model import todict
|
||||||
from tests.builder import a, instance, volume, volume_type
|
from tests.builder import a, instance, volume, volume_type
|
||||||
@ -128,7 +130,7 @@ class DatabaseAdapterTest(unittest.TestCase):
|
|||||||
[self.db.entity.insert(todict(fake_entity)) for fake_entity in fake_instances + fake_volumes]
|
[self.db.entity.insert(todict(fake_entity)) for fake_entity in fake_instances + fake_volumes]
|
||||||
|
|
||||||
entities = self.adapter.list_entities("project_id", datetime(
|
entities = self.adapter.list_entities("project_id", datetime(
|
||||||
2014, 1, 1, 0, 0, 0), datetime(2014, 1, 1, 12, 0, 0), "instance")
|
2014, 1, 1, 0, 0, 0, tzinfo=pytz.utc), datetime(2014, 1, 1, 12, 0, 0, tzinfo=pytz.utc), "instance")
|
||||||
assert_that(entities, contains_inanyorder(*fake_instances))
|
assert_that(entities, contains_inanyorder(*fake_instances))
|
||||||
|
|
||||||
def test_list_instances_with_decode_output(self):
|
def test_list_instances_with_decode_output(self):
|
||||||
@ -167,7 +169,7 @@ class DatabaseAdapterTest(unittest.TestCase):
|
|||||||
[self.db.entity.insert(todict(fake_entity)) for fake_entity in fake_instances]
|
[self.db.entity.insert(todict(fake_entity)) for fake_entity in fake_instances]
|
||||||
|
|
||||||
entities = self.adapter.list_entities("project_id", datetime(
|
entities = self.adapter.list_entities("project_id", datetime(
|
||||||
2014, 1, 1, 0, 0, 0), datetime(2014, 1, 1, 12, 0, 0), "instance")
|
2014, 1, 1, 0, 0, 0, tzinfo=pytz.utc), datetime(2014, 1, 1, 12, 0, 0, tzinfo=pytz.utc), "instance")
|
||||||
assert_that(entities, contains_inanyorder(*expected_instances))
|
assert_that(entities, contains_inanyorder(*expected_instances))
|
||||||
self.assert_entities_metadata_have_been_sanitize(entities)
|
self.assert_entities_metadata_have_been_sanitize(entities)
|
||||||
|
|
||||||
@ -195,10 +197,27 @@ class DatabaseAdapterTest(unittest.TestCase):
|
|||||||
for fake_entity in fake_entities_in_period + fake_entities_out_period]
|
for fake_entity in fake_entities_in_period + fake_entities_out_period]
|
||||||
|
|
||||||
entities = self.adapter.list_entities("project_id", datetime(
|
entities = self.adapter.list_entities("project_id", datetime(
|
||||||
2014, 1, 1, 6, 0, 0), datetime(2014, 1, 1, 9, 0, 0))
|
2014, 1, 1, 6, 0, 0, tzinfo=pytz.utc), datetime(2014, 1, 1, 9, 0, 0, tzinfo=pytz.utc))
|
||||||
assert_that(entities, contains_inanyorder(*fake_entities_in_period))
|
assert_that(entities, contains_inanyorder(*fake_entities_in_period))
|
||||||
|
|
||||||
def test_update_entity(self):
|
def test_list_entities_by_id(self):
|
||||||
|
start = datetime(2016, 3, 1, 0, 0, 0, 0, pytz.utc)
|
||||||
|
end = datetime(2016, 3, 3, 0, 0, 0, 0, pytz.utc)
|
||||||
|
proper_instance = a(instance().with_id("id1").with_start(2016, 3, 1, 0, 0, 0).with_end(2016, 3, 2, 0, 0, 0))
|
||||||
|
instances = [
|
||||||
|
proper_instance,
|
||||||
|
a(instance()
|
||||||
|
.with_id("id1")
|
||||||
|
.with_start(2016, 3, 2, 0, 0, 0)
|
||||||
|
.with_no_end()),
|
||||||
|
]
|
||||||
|
[self.db.entity.insert(todict(fake_instance)) for fake_instance in instances]
|
||||||
|
|
||||||
|
instance_list = self.adapter.list_entities_by_id("id1", start, end)
|
||||||
|
|
||||||
|
assert_that(instance_list, contains_inanyorder(*[proper_instance]))
|
||||||
|
|
||||||
|
def test_update_active_entity(self):
|
||||||
fake_entity = a(instance())
|
fake_entity = a(instance())
|
||||||
end_date = datetime(2015, 10, 21, 16, 29, 0)
|
end_date = datetime(2015, 10, 21, 16, 29, 0)
|
||||||
|
|
||||||
@ -207,6 +226,17 @@ class DatabaseAdapterTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(self.db.entity.find_one({"entity_id": fake_entity.entity_id})["end"], end_date)
|
self.assertEqual(self.db.entity.find_one({"entity_id": fake_entity.entity_id})["end"], end_date)
|
||||||
|
|
||||||
|
def test_update_closed_entity(self):
|
||||||
|
fake_entity = a(instance().with_end(2016, 3, 2, 0, 0, 0))
|
||||||
|
|
||||||
|
self.db.entity.insert(todict(fake_entity))
|
||||||
|
fake_entity.flavor = "my_new_flavor"
|
||||||
|
self.adapter.update_closed_entity(fake_entity, data={"flavor": fake_entity.flavor})
|
||||||
|
|
||||||
|
db_entity = self.db.entity.find_one({"entity_id": fake_entity.entity_id})
|
||||||
|
assert_that(db_entity['flavor'], fake_entity.flavor)
|
||||||
|
assert_that(db_entity['end'], fake_entity.end)
|
||||||
|
|
||||||
def test_replace_entity(self):
|
def test_replace_entity(self):
|
||||||
fake_entity = a(instance())
|
fake_entity = a(instance())
|
||||||
fake_entity.os.distro = "Centos"
|
fake_entity.os.distro = "Centos"
|
||||||
|
@ -45,7 +45,7 @@ class EntityBuilder(Builder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def with_start(self, year, month, day, hour, minute, second):
|
def with_start(self, year, month, day, hour, minute, second):
|
||||||
self.with_datetime_start(datetime(year, month, day, hour, minute, second))
|
self.with_datetime_start(datetime(year, month, day, hour, minute, second, tzinfo=pytz.utc))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_datetime_start(self, date):
|
def with_datetime_start(self, date):
|
||||||
@ -53,7 +53,7 @@ class EntityBuilder(Builder):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def with_end(self, year, month, day, hour, minute, second):
|
def with_end(self, year, month, day, hour, minute, second):
|
||||||
self.dict_object["end"] = datetime(year, month, day, hour, minute, second)
|
self.dict_object["end"] = datetime(year, month, day, hour, minute, second, tzinfo=pytz.utc)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_no_end(self):
|
def with_no_end(self):
|
||||||
|
@ -15,20 +15,21 @@
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
import pytz
|
|
||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pytz
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from hamcrest import raises, calling, assert_that
|
from hamcrest import raises, calling, assert_that
|
||||||
from flexmock import flexmock, flexmock_teardown
|
from flexmock import flexmock, flexmock_teardown
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
from tests.builder import a, instance, volume, volume_type
|
|
||||||
|
|
||||||
|
from almanach.common.exceptions.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||||
|
from almanach.common.exceptions.multiple_entities_matching_query import MultipleEntitiesMatchingQuery
|
||||||
|
from tests.builder import a, instance, volume, volume_type
|
||||||
from almanach import config
|
from almanach import config
|
||||||
from almanach.common.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||||
from almanach.common.date_format_exception import DateFormatException
|
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||||
from almanach.common.validation_exception import InvalidAttributeException
|
|
||||||
from almanach.core.controller import Controller
|
from almanach.core.controller import Controller
|
||||||
from almanach.core.model import Instance, Volume
|
from almanach.core.model import Instance, Volume
|
||||||
|
|
||||||
@ -99,6 +100,64 @@ class ControllerTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.controller.resize_instance(fake_instance.entity_id, "newly_flavor", dates_str)
|
self.controller.resize_instance(fake_instance.entity_id, "newly_flavor", dates_str)
|
||||||
|
|
||||||
|
def test_update_entity_closed_entity_flavor(self):
|
||||||
|
start = datetime(2016, 3, 1, 0, 0, 0, 0, pytz.utc)
|
||||||
|
end = datetime(2016, 3, 3, 0, 0, 0, 0, pytz.utc)
|
||||||
|
flavor = 'a_new_flavor'
|
||||||
|
fake_instance1 = a(instance().with_start(2016, 3, 1, 0, 0, 0).with_end(2016, 3, 2, 0, 0, 0))
|
||||||
|
|
||||||
|
(flexmock(self.database_adapter)
|
||||||
|
.should_receive("list_entities_by_id")
|
||||||
|
.with_args(fake_instance1.entity_id, start, end)
|
||||||
|
.and_return([fake_instance1])
|
||||||
|
.twice())
|
||||||
|
|
||||||
|
(flexmock(self.database_adapter)
|
||||||
|
.should_receive("update_closed_entity")
|
||||||
|
.with_args(entity=fake_instance1, data={"flavor": flavor})
|
||||||
|
.once())
|
||||||
|
|
||||||
|
self.controller.update_inactive_entity(
|
||||||
|
instance_id=fake_instance1.entity_id,
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
flavor=flavor,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_one_close_entity_return_multiple_entities(self):
|
||||||
|
fake_instances = [a(instance()), a(instance())]
|
||||||
|
|
||||||
|
(flexmock(self.database_adapter)
|
||||||
|
.should_receive("list_entities_by_id")
|
||||||
|
.with_args(fake_instances[0].entity_id, fake_instances[0].start, fake_instances[0].end)
|
||||||
|
.and_return(fake_instances)
|
||||||
|
.once())
|
||||||
|
|
||||||
|
assert_that(
|
||||||
|
calling(self.controller.update_inactive_entity).with_args(instance_id=fake_instances[0].entity_id,
|
||||||
|
start=fake_instances[0].start,
|
||||||
|
end=fake_instances[0].end,
|
||||||
|
flavor=fake_instances[0].flavor),
|
||||||
|
raises(MultipleEntitiesMatchingQuery)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_one_close_entity_return_no_entity(self):
|
||||||
|
fake_instances = a(instance())
|
||||||
|
|
||||||
|
(flexmock(self.database_adapter)
|
||||||
|
.should_receive("list_entities_by_id")
|
||||||
|
.with_args(fake_instances.entity_id, fake_instances.start, fake_instances.end)
|
||||||
|
.and_return([])
|
||||||
|
.once())
|
||||||
|
|
||||||
|
assert_that(
|
||||||
|
calling(self.controller.update_inactive_entity).with_args(instance_id=fake_instances.entity_id,
|
||||||
|
start=fake_instances.start,
|
||||||
|
end=fake_instances.end,
|
||||||
|
flavor=fake_instances.flavor),
|
||||||
|
raises(AlmanachEntityNotFoundException)
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_active_instance_entity_with_a_new_flavor(self):
|
def test_update_active_instance_entity_with_a_new_flavor(self):
|
||||||
flavor = u"my flavor name"
|
flavor = u"my flavor name"
|
||||||
fake_instance1 = a(instance())
|
fake_instance1 = a(instance())
|
||||||
|
@ -13,27 +13,26 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import flask
|
|
||||||
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import flask
|
||||||
from voluptuous import Invalid
|
from voluptuous import Invalid
|
||||||
|
|
||||||
from almanach.common.validation_exception import InvalidAttributeException
|
|
||||||
from flexmock import flexmock, flexmock_teardown
|
from flexmock import flexmock, flexmock_teardown
|
||||||
|
|
||||||
from hamcrest import assert_that, has_key, equal_to, has_length, has_entry, has_entries, is_
|
from hamcrest import assert_that, has_key, equal_to, has_length, has_entry, has_entries, is_
|
||||||
|
|
||||||
|
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||||
from almanach import config
|
from almanach import config
|
||||||
from almanach.common.date_format_exception import DateFormatException
|
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||||
from almanach.common.almanach_exception import AlmanachException
|
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||||
from almanach.adapters import api_route_v1 as api_route
|
from almanach.adapters import api_route_v1 as api_route
|
||||||
|
|
||||||
from tests.builder import a, instance, volume_type
|
from tests.builder import a, instance, volume_type
|
||||||
|
|
||||||
|
|
||||||
class ApiTest(TestCase):
|
class ApiTest(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.controller = flexmock()
|
self.controller = flexmock()
|
||||||
api_route.controller = self.controller
|
api_route.controller = self.controller
|
||||||
@ -59,9 +58,9 @@ class ApiTest(TestCase):
|
|||||||
|
|
||||||
def test_instances_with_authentication(self):
|
def test_instances_with_authentication(self):
|
||||||
self.having_config('api_auth_token', 'some token value')
|
self.having_config('api_auth_token', 'some token value')
|
||||||
self.controller.should_receive('list_instances')\
|
self.controller.should_receive('list_instances') \
|
||||||
.with_args('TENANT_ID', a_date_matching("2014-01-01 00:00:00.0000"),
|
.with_args('TENANT_ID', a_date_matching("2014-01-01 00:00:00.0000"),
|
||||||
a_date_matching("2014-02-01 00:00:00.0000"))\
|
a_date_matching("2014-02-01 00:00:00.0000")) \
|
||||||
.and_return([a(instance().with_id('123'))])
|
.and_return([a(instance().with_id('123'))])
|
||||||
|
|
||||||
code, result = self.api_get('/project/TENANT_ID/instances',
|
code, result = self.api_get('/project/TENANT_ID/instances',
|
||||||
@ -76,6 +75,42 @@ class ApiTest(TestCase):
|
|||||||
assert_that(result[0], has_key('entity_id'))
|
assert_that(result[0], has_key('entity_id'))
|
||||||
assert_that(result[0]['entity_id'], equal_to('123'))
|
assert_that(result[0]['entity_id'], equal_to('123'))
|
||||||
|
|
||||||
|
def test_update_instance_flavor_for_terminated_instance(self):
|
||||||
|
some_new_flavor = 'some_new_flavor'
|
||||||
|
data = dict(flavor=some_new_flavor)
|
||||||
|
start = '2016-03-01 00:00:00.000000'
|
||||||
|
end = '2016-03-03 00:00:00.000000'
|
||||||
|
|
||||||
|
self.having_config('api_auth_token', 'some token value')
|
||||||
|
|
||||||
|
self.controller.should_receive('update_inactive_entity') \
|
||||||
|
.with_args(
|
||||||
|
instance_id="INSTANCE_ID",
|
||||||
|
start=a_date_matching(start),
|
||||||
|
end=a_date_matching(end),
|
||||||
|
flavor=some_new_flavor,
|
||||||
|
).and_return(a(
|
||||||
|
instance().
|
||||||
|
with_id('INSTANCE_ID').
|
||||||
|
with_start(2016, 03, 01, 00, 0, 00).
|
||||||
|
with_end(2016, 03, 03, 00, 0, 00).
|
||||||
|
with_flavor(some_new_flavor))
|
||||||
|
)
|
||||||
|
|
||||||
|
code, result = self.api_put(
|
||||||
|
'/entity/instance/INSTANCE_ID',
|
||||||
|
headers={'X-Auth-Token': 'some token value'},
|
||||||
|
query_string={
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
},
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
assert_that(code, equal_to(200))
|
||||||
|
assert_that(result, has_key('entity_id'))
|
||||||
|
assert_that(result, has_key('flavor'))
|
||||||
|
assert_that(result['flavor'], is_(some_new_flavor))
|
||||||
|
|
||||||
def test_update_instance_entity_with_a_new_start_date(self):
|
def test_update_instance_entity_with_a_new_start_date(self):
|
||||||
data = {
|
data = {
|
||||||
"start_date": "2014-01-01 00:00:00.0000",
|
"start_date": "2014-01-01 00:00:00.0000",
|
||||||
@ -83,7 +118,7 @@ class ApiTest(TestCase):
|
|||||||
|
|
||||||
self.having_config('api_auth_token', 'some token value')
|
self.having_config('api_auth_token', 'some token value')
|
||||||
|
|
||||||
self.controller.should_receive('update_active_instance_entity')\
|
self.controller.should_receive('update_active_instance_entity') \
|
||||||
.with_args(
|
.with_args(
|
||||||
instance_id="INSTANCE_ID",
|
instance_id="INSTANCE_ID",
|
||||||
start_date=data["start_date"],
|
start_date=data["start_date"],
|
||||||
@ -99,7 +134,7 @@ class ApiTest(TestCase):
|
|||||||
assert_that(result, has_key('entity_id'))
|
assert_that(result, has_key('entity_id'))
|
||||||
assert_that(result, has_key('start'))
|
assert_that(result, has_key('start'))
|
||||||
assert_that(result, has_key('end'))
|
assert_that(result, has_key('end'))
|
||||||
assert_that(result['start'], is_("2014-01-01 00:00:00"))
|
assert_that(result['start'], is_("2014-01-01 00:00:00+00:00"))
|
||||||
|
|
||||||
def test_instances_with_wrong_authentication(self):
|
def test_instances_with_wrong_authentication(self):
|
||||||
self.having_config('api_auth_token', 'some token value')
|
self.having_config('api_auth_token', 'some token value')
|
||||||
@ -880,7 +915,7 @@ class ApiTest(TestCase):
|
|||||||
headers = {}
|
headers = {}
|
||||||
headers['Accept'] = accept
|
headers['Accept'] = accept
|
||||||
result = getattr(http_client, method)(url, data=json.dumps(data), query_string=query_string, headers=headers)
|
result = getattr(http_client, method)(url, data=json.dumps(data), query_string=query_string, headers=headers)
|
||||||
return_data = json.loads(result.data)\
|
return_data = json.loads(result.data) \
|
||||||
if result.headers.get('Content-Type') == 'application/json' \
|
if result.headers.get('Content-Type') == 'application/json' \
|
||||||
else result.data
|
else result.data
|
||||||
return result.status_code, return_data
|
return result.status_code, return_data
|
||||||
@ -925,7 +960,6 @@ class ApiTest(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class DateMatcher(object):
|
class DateMatcher(object):
|
||||||
|
|
||||||
def __init__(self, date):
|
def __init__(self, date):
|
||||||
self.date = date
|
self.date = date
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from almanach.common.validation_exception import InvalidAttributeException
|
|
||||||
from almanach.validators.instance_validator import InstanceValidator
|
|
||||||
from hamcrest import assert_that, calling, raises, is_
|
from hamcrest import assert_that, calling, raises, is_
|
||||||
|
|
||||||
|
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||||
|
from almanach.validators.instance_validator import InstanceValidator
|
||||||
|
|
||||||
|
|
||||||
class InstanceValidatorTests(unittest.TestCase):
|
class InstanceValidatorTests(unittest.TestCase):
|
||||||
def test_validate_update_with_invalid_attribute(self):
|
def test_validate_update_with_invalid_attribute(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user