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 LICENSE /opt/almanach/src/
|
||||
ADD almanach/resources/config/almanach.cfg /etc/almanach.cfg
|
||||
COPY docker-entrypoint.sh /opt/almanach/entrypoint.sh
|
||||
|
||||
WORKDIR /opt/almanach
|
||||
|
||||
RUN cd src && \
|
||||
RUN cd /opt/almanach/src && \
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
export RABBITMQ_URL="amqp://openstack:openstack@my-hostname:5672/"
|
||||
export MONGODB_URL="mongodb://almanach:almanach@my-hostname:27017/almanach"
|
||||
export RABBITMQ_URL="amqp://guest:guest@messaging:5672/"
|
||||
export MONGODB_URL="mongodb://almanach:almanach@database:27017/almanach"
|
||||
|
||||
docker-compose build
|
||||
docker-compose up
|
||||
|
@ -14,16 +14,20 @@
|
||||
|
||||
import logging
|
||||
import json
|
||||
import jsonpickle
|
||||
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from almanach.common.validation_exception import InvalidAttributeException
|
||||
|
||||
import jsonpickle
|
||||
|
||||
from flask import Blueprint, Response, request
|
||||
|
||||
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.common.date_format_exception import DateFormatException
|
||||
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||
|
||||
api = Blueprint("api", __name__)
|
||||
controller = None
|
||||
@ -53,9 +57,18 @@ def to_json(api_call):
|
||||
except InvalidAttributeException as e:
|
||||
logging.warning(e.get_error_message())
|
||||
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:
|
||||
logging.exception(e)
|
||||
return Response(encode({"error": e.message}), 500, {"Content-Type": "application/json"})
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ -256,7 +269,12 @@ def list_entity(project_id):
|
||||
def update_instance_entity(instance_id):
|
||||
data = json.loads(request.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"])
|
||||
|
@ -13,15 +13,16 @@
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import pymongo
|
||||
|
||||
import pymongo
|
||||
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 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 _connection(self, *args, **kwargs):
|
||||
@ -55,7 +56,6 @@ def ensureindex(db):
|
||||
|
||||
|
||||
class DatabaseAdapter(object):
|
||||
|
||||
def __init__(self):
|
||||
self.db = None
|
||||
|
||||
@ -90,6 +90,22 @@ class DatabaseAdapter(object):
|
||||
entities = self._get_entities_from_db(args)
|
||||
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
|
||||
def insert_entity(self, entity):
|
||||
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.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||
|
||||
|
||||
class DateFormatException(Exception):
|
||||
class DateFormatException(AlmanachException):
|
||||
|
||||
def __init__(self, message=None):
|
||||
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.
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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):
|
||||
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.path as os_path
|
||||
|
||||
from almanach.common.almanach_exception import AlmanachException
|
||||
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||
|
||||
configuration = ConfigParser.RawConfigParser()
|
||||
|
||||
|
@ -13,14 +13,16 @@
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import pytz
|
||||
from dateutil import parser as date_parser
|
||||
|
||||
from pkg_resources import get_distribution
|
||||
|
||||
from almanach.common.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||
from almanach.common.date_format_exception import DateFormatException
|
||||
from almanach.common.exceptions.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||
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.validators.instance_validator import InstanceValidator
|
||||
from almanach import config
|
||||
@ -107,6 +109,19 @@ class Controller(object):
|
||||
instance.last_event = rebuild_date
|
||||
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):
|
||||
try:
|
||||
InstanceValidator().validate_update(kwargs)
|
||||
@ -155,18 +170,20 @@ class Controller(object):
|
||||
raise e
|
||||
|
||||
def _update_instance_object(self, instance, **kwargs):
|
||||
for key, value in self._transform_attribute_to_match_entity_attribute(**kwargs).items():
|
||||
setattr(instance, 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():
|
||||
value = kwargs.get(key)
|
||||
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))
|
||||
if kwargs.get(key):
|
||||
entity[attribute] = self._validate_and_parse_date(kwargs.get(key))
|
||||
|
||||
for attribute in ["name", "flavor", "os", "metadata"]:
|
||||
value = kwargs.get(attribute)
|
||||
if value:
|
||||
setattr(instance, attribute, value)
|
||||
logging.info(
|
||||
"Updating entity for instance '{0}' with {1}={2}".format(instance.entity_id, attribute, value))
|
||||
if kwargs.get(attribute):
|
||||
entity[attribute] = kwargs.get(attribute)
|
||||
return entity
|
||||
|
||||
def _volume_attach_instance(self, volume_id, date, attachments):
|
||||
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 almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||
|
||||
|
||||
class InstanceValidator(object):
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
command: almanach api /etc/almanach.cfg --host 0.0.0.0
|
||||
command: api
|
||||
environment:
|
||||
MONGODB_URL: ${MONGODB_URL}
|
||||
ports:
|
||||
@ -13,7 +13,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
command: almanach collector /etc/almanach.cfg
|
||||
command: collector
|
||||
environment:
|
||||
MONGODB_URL: ${MONGODB_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.
|
||||
|
||||
import unittest
|
||||
import pytz
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
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 almanach.adapters.bus_adapter import BusAdapter
|
||||
|
||||
|
@ -12,18 +12,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import pkg_resources
|
||||
import unittest
|
||||
import mongomock
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pkg_resources
|
||||
import mongomock
|
||||
from flexmock import flexmock, flexmock_teardown
|
||||
from hamcrest import assert_that, contains_inanyorder
|
||||
|
||||
from pymongo import MongoClient
|
||||
import pytz
|
||||
|
||||
from almanach.adapters.database_adapter import DatabaseAdapter
|
||||
from almanach.common.volume_type_not_found_exception import VolumeTypeNotFoundException
|
||||
from almanach.common.almanach_exception import AlmanachException
|
||||
from almanach.common.exceptions.volume_type_not_found_exception import VolumeTypeNotFoundException
|
||||
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||
from almanach import config
|
||||
from almanach.core.model import todict
|
||||
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]
|
||||
|
||||
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))
|
||||
|
||||
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]
|
||||
|
||||
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))
|
||||
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]
|
||||
|
||||
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))
|
||||
|
||||
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())
|
||||
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)
|
||||
|
||||
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):
|
||||
fake_entity = a(instance())
|
||||
fake_entity.os.distro = "Centos"
|
||||
|
@ -45,7 +45,7 @@ class EntityBuilder(Builder):
|
||||
return self
|
||||
|
||||
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
|
||||
|
||||
def with_datetime_start(self, date):
|
||||
@ -53,7 +53,7 @@ class EntityBuilder(Builder):
|
||||
return self
|
||||
|
||||
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
|
||||
|
||||
def with_no_end(self):
|
||||
|
@ -15,20 +15,21 @@
|
||||
import sys
|
||||
import logging
|
||||
import unittest
|
||||
import pytz
|
||||
|
||||
from copy import copy
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from hamcrest import raises, calling, assert_that
|
||||
from flexmock import flexmock, flexmock_teardown
|
||||
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.common.almanach_entity_not_found_exception import AlmanachEntityNotFoundException
|
||||
from almanach.common.date_format_exception import DateFormatException
|
||||
from almanach.common.validation_exception import InvalidAttributeException
|
||||
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||
from almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||
from almanach.core.controller import Controller
|
||||
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)
|
||||
|
||||
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):
|
||||
flavor = u"my flavor name"
|
||||
fake_instance1 = a(instance())
|
||||
|
@ -13,27 +13,26 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import flask
|
||||
|
||||
from uuid import uuid4
|
||||
from unittest import TestCase
|
||||
from datetime import datetime
|
||||
|
||||
import flask
|
||||
from voluptuous import Invalid
|
||||
|
||||
from almanach.common.validation_exception import InvalidAttributeException
|
||||
from flexmock import flexmock, flexmock_teardown
|
||||
|
||||
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.common.date_format_exception import DateFormatException
|
||||
from almanach.common.almanach_exception import AlmanachException
|
||||
from almanach.common.exceptions.date_format_exception import DateFormatException
|
||||
from almanach.common.exceptions.almanach_exception import AlmanachException
|
||||
from almanach.adapters import api_route_v1 as api_route
|
||||
|
||||
from tests.builder import a, instance, volume_type
|
||||
|
||||
|
||||
class ApiTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.controller = flexmock()
|
||||
api_route.controller = self.controller
|
||||
@ -59,9 +58,9 @@ class ApiTest(TestCase):
|
||||
|
||||
def test_instances_with_authentication(self):
|
||||
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"),
|
||||
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'))])
|
||||
|
||||
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]['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):
|
||||
data = {
|
||||
"start_date": "2014-01-01 00:00:00.0000",
|
||||
@ -83,10 +118,10 @@ class ApiTest(TestCase):
|
||||
|
||||
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(
|
||||
instance_id="INSTANCE_ID",
|
||||
start_date=data["start_date"],
|
||||
instance_id="INSTANCE_ID",
|
||||
start_date=data["start_date"],
|
||||
).and_return(a(instance().with_id('INSTANCE_ID').with_start(2014, 01, 01, 00, 0, 00)))
|
||||
|
||||
code, result = self.api_put(
|
||||
@ -99,7 +134,7 @@ class ApiTest(TestCase):
|
||||
assert_that(result, has_key('entity_id'))
|
||||
assert_that(result, has_key('start'))
|
||||
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):
|
||||
self.having_config('api_auth_token', 'some token value')
|
||||
@ -205,8 +240,8 @@ class ApiTest(TestCase):
|
||||
|
||||
self.controller.should_receive('create_volume_type') \
|
||||
.with_args(
|
||||
volume_type_id=data['type_id'],
|
||||
volume_type_name=data['type_name']) \
|
||||
volume_type_id=data['type_id'],
|
||||
volume_type_name=data['type_name']) \
|
||||
.once()
|
||||
|
||||
code, result = self.api_post('/volume_type', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
@ -323,9 +358,9 @@ class ApiTest(TestCase):
|
||||
)
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -381,9 +416,9 @@ class ApiTest(TestCase):
|
||||
code, result = self.api_delete('/volume/VOLUME_ID', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -434,9 +469,9 @@ class ApiTest(TestCase):
|
||||
code, result = self.api_put('/volume/VOLUME_ID/resize', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -494,9 +529,9 @@ class ApiTest(TestCase):
|
||||
code, result = self.api_put('/volume/VOLUME_ID/attach', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -550,9 +585,9 @@ class ApiTest(TestCase):
|
||||
code, result = self.api_put('/volume/VOLUME_ID/detach', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -642,9 +677,9 @@ class ApiTest(TestCase):
|
||||
)
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -723,9 +758,9 @@ class ApiTest(TestCase):
|
||||
code, result = self.api_delete('/instance/INSTANCE_ID', data=data, headers={'X-Auth-Token': 'some token value'})
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -770,9 +805,9 @@ class ApiTest(TestCase):
|
||||
)
|
||||
assert_that(result, has_entries(
|
||||
{
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
"error": "The provided date has an invalid format. "
|
||||
"Format should be of yyyy-mm-ddThh:mm:ss.msZ, ex: 2015-01-31T18:24:34.1523Z"
|
||||
}
|
||||
))
|
||||
assert_that(code, equal_to(400))
|
||||
|
||||
@ -794,11 +829,11 @@ class ApiTest(TestCase):
|
||||
}
|
||||
self.controller.should_receive('rebuild_instance') \
|
||||
.with_args(
|
||||
instance_id=instance_id,
|
||||
distro=data.get('distro'),
|
||||
version=data.get('version'),
|
||||
os_type=data.get('os_type'),
|
||||
rebuild_date=data.get('rebuild_date')) \
|
||||
instance_id=instance_id,
|
||||
distro=data.get('distro'),
|
||||
version=data.get('version'),
|
||||
os_type=data.get('os_type'),
|
||||
rebuild_date=data.get('rebuild_date')) \
|
||||
.once()
|
||||
|
||||
code, result = self.api_put(
|
||||
@ -880,7 +915,7 @@ class ApiTest(TestCase):
|
||||
headers = {}
|
||||
headers['Accept'] = accept
|
||||
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' \
|
||||
else result.data
|
||||
return result.status_code, return_data
|
||||
@ -914,9 +949,9 @@ class ApiTest(TestCase):
|
||||
.and_raise(InvalidAttributeException(errors))
|
||||
|
||||
code, result = self.api_put(
|
||||
'/entity/instance/INSTANCE_ID',
|
||||
data=data,
|
||||
headers={'X-Auth-Token': 'some token value'}
|
||||
'/entity/instance/INSTANCE_ID',
|
||||
data=data,
|
||||
headers={'X-Auth-Token': 'some token value'}
|
||||
)
|
||||
assert_that(result, has_entries({
|
||||
"error": formatted_errors
|
||||
@ -925,7 +960,6 @@ class ApiTest(TestCase):
|
||||
|
||||
|
||||
class DateMatcher(object):
|
||||
|
||||
def __init__(self, date):
|
||||
self.date = date
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
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 almanach.common.exceptions.validation_exception import InvalidAttributeException
|
||||
from almanach.validators.instance_validator import InstanceValidator
|
||||
|
||||
|
||||
class InstanceValidatorTests(unittest.TestCase):
|
||||
def test_validate_update_with_invalid_attribute(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user