Modify api to support edition of closed entity

This commit is contained in:
Marx314 2016-05-18 12:05:09 -04:00 committed by Frédéric Guillot
parent 47ab4568ce
commit a81c0554b5
24 changed files with 364 additions and 119 deletions

View File

@ -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"]

View File

@ -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

View File

@ -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"])

View File

@ -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())

View File

@ -1,2 +0,0 @@
class AlmanachEntityNotFoundException(Exception):
pass

View File

View 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

View File

@ -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:

View 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 MultipleEntitiesMatchingQuery(AlmanachException):
pass

View 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

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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):

View File

@ -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
View 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 "$@"

View File

@ -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

View File

@ -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"

View File

@ -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):

View File

@ -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())

View File

@ -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

View File

@ -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):