diff --git a/src/ralph/assets/api/serializers.py b/src/ralph/assets/api/serializers.py index 310025b8c9..157ec1e504 100644 --- a/src/ralph/assets/api/serializers.py +++ b/src/ralph/assets/api/serializers.py @@ -30,6 +30,7 @@ from ralph.assets.models.components import Ethernet from ralph.lib.custom_fields.api import WithCustomFieldsSerializerMixin from ralph.licences.api_simple import SimpleBaseObjectLicenceSerializer +from ralph.networks.api_simple import IPAddressSimpleSerializer class BusinessSegmentSerializer(RalphAPISerializer): @@ -255,7 +256,16 @@ class Meta(BaseObjectSerializer.Meta): model = Asset -class EthernetSerializer(RalphAPISerializer): +class EthernetSimpleSerializer(RalphAPISerializer): + ipaddress = IPAddressSimpleSerializer() + + class Meta: + model = Ethernet + fields = ('id', 'mac', 'ipaddress', 'url') + + +class EthernetSerializer(EthernetSimpleSerializer): + class Meta: model = Ethernet depth = 1 diff --git a/src/ralph/assets/api/views.py b/src/ralph/assets/api/views.py index 4e4637b664..71422328d0 100644 --- a/src/ralph/assets/api/views.py +++ b/src/ralph/assets/api/views.py @@ -6,6 +6,7 @@ from ralph.api.utils import PolymorphicViewSetMixin from ralph.assets import models from ralph.assets.api import serializers +from ralph.lib.custom_fields.models import CustomFieldValue from ralph.licences.api import BaseObjectLicenceViewSet from ralph.licences.models import BaseObjectLicence from ralph.networks.models import IPAddress @@ -77,6 +78,10 @@ class BaseObjectViewSet(PolymorphicViewSetMixin, RalphAPIViewSet): Prefetch('licences', queryset=BaseObjectLicence.objects.select_related( *BaseObjectLicenceViewSet.select_related )), + Prefetch( + 'custom_fields', + queryset=CustomFieldValue.objects.select_related('custom_field') + ) ] filter_fields = [ 'id', 'service_env', 'service_env', 'service_env__service__uid', @@ -100,7 +105,7 @@ class AssetHolderViewSet(RalphAPIViewSet): class EthernetViewSet(RalphAPIViewSet): queryset = models.Ethernet.objects.all() serializer_class = serializers.EthernetSerializer - filter_fields = ['base_object'] + filter_fields = ['base_object', 'ipaddress__address'] prefetch_related = ['model', 'base_object', 'base_object__tags'] def destroy(self, request, *args, **kwargs): diff --git a/src/ralph/assets/tests/factories.py b/src/ralph/assets/tests/factories.py index b242d390e1..c88d0638b2 100644 --- a/src/ralph/assets/tests/factories.py +++ b/src/ralph/assets/tests/factories.py @@ -15,7 +15,7 @@ ServiceEnvironment ) from ralph.assets.models.base import BaseObject -from ralph.assets.models.choices import ComponentType, ObjectModelType +from ralph.assets.models.choices import ObjectModelType from ralph.assets.models.components import Ethernet from ralph.assets.models.configuration import ( ConfigurationClass, @@ -185,6 +185,13 @@ class Meta: django_get_or_create = ['label'] +class EthernetWithIPAddressFactory(EthernetFactory): + ipaddress = factory.RelatedFactory( + 'ralph.networks.tests.factories.IPAddressWithNetworkFactory', + 'ethernet' + ) + + class ConfigurationModuleFactory(DjangoModelFactory): name = factory.Iterator(['ralph', 'allegro', 'auth', 'order']) diff --git a/src/ralph/assets/tests/test_api.py b/src/ralph/assets/tests/test_api.py index 1de9c0fc88..2e5342b454 100644 --- a/src/ralph/assets/tests/test_api.py +++ b/src/ralph/assets/tests/test_api.py @@ -760,6 +760,28 @@ def setUp(self): super().setUp() self.ip = IPAddressFactory(dhcp_expose=True) self.eth = self.ip.ethernet + self.eth2 = EthernetFactory() + + def test_get_list_of_ethernets(self): + url = reverse('ethernet-list') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['count'], 2) + + def test_get_ethernet_with_ip_details(self): + url = reverse('ethernet-detail', args=(self.eth.id,)) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['ipaddress'], { + 'id': self.ip.id, + 'address': self.ip.address, + 'hostname': self.ip.hostname, + 'dhcp_expose': self.ip.dhcp_expose, + 'is_management': self.ip.is_management, + 'url': self.get_full_url( + reverse('ipaddress-detail', args=(self.ip.id,)) + ) + }) def test_cannot_delete_when_exposed_in_dhcp(self): url = reverse('ethernet-detail', args=(self.eth.id,)) @@ -769,3 +791,11 @@ def test_cannot_delete_when_exposed_in_dhcp(self): 'Could not delete Ethernet when it is exposed in DHCP', response.data ) + + def test_filter_by_ipaddress(self): + url = '{}?ipaddress__address={}'.format( + reverse('ethernet-list'), self.ip.address + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['count'], 1) diff --git a/src/ralph/data_center/admin.py b/src/ralph/data_center/admin.py index a3a82d92e0..a18a54a93d 100644 --- a/src/ralph/data_center/admin.py +++ b/src/ralph/data_center/admin.py @@ -258,7 +258,8 @@ class DataCenterAssetAdmin( list_select_related = [ 'model', 'model__manufacturer', 'model__category', 'rack', 'rack__server_room', 'rack__server_room__data_center', 'service_env', - 'service_env__service', 'service_env__environment', 'configuration_path' + 'service_env__service', 'service_env__environment', + 'configuration_path__module', ] raw_id_fields = [ 'model', 'rack', 'service_env', 'parent', 'budget_info', diff --git a/src/ralph/data_center/api/serializers.py b/src/ralph/data_center/api/serializers.py index 415dffab68..b085b84e63 100644 --- a/src/ralph/data_center/api/serializers.py +++ b/src/ralph/data_center/api/serializers.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- -from rest_framework import serializers +from rest_framework import fields, serializers from ralph.api import RalphAPISerializer -from ralph.assets.api.serializers import AssetSerializer, BaseObjectSerializer -from ralph.assets.models.components import Ethernet +from ralph.assets.api.serializers import ( + AssetSerializer, + BaseObjectSerializer, + EthernetSimpleSerializer +) from ralph.data_center.models import ( Accessory, BaseObjectCluster, @@ -81,21 +84,27 @@ class Meta: exclude = ('accessories',) -class EthernetSerializer(RalphAPISerializer): - class Meta: - model = Ethernet - depth = 1 - - +# used by DataCenterAsset and VirtualServer serializers class ComponentSerializerMixin(serializers.Serializer): - # ethernets = EthernetSerializer() - # disk_shares = .. - # ipaddresses = SimpleIPAddressesSerializer() - # mac_addresses = SimpleRackSerializer() - service = serializers.SerializerMethodField('get_service_env') - - def get_service_env(self, obj): - return '' + ethernet = EthernetSimpleSerializer(many=True) + ipaddresses = fields.SerializerMethodField() + + def get_ipaddresses(self, instance): + """ + Return list of ip addresses for passed instance. + + Returns: + list of ip addresses (as strings) + """ + # don't use `ipaddresses` property here to make use of + # `ethernet__ipaddresses` in prefetch related + ipaddresses = [] + for eth in instance.ethernet.all(): + try: + ipaddresses.append(eth.ipaddress.address) + except AttributeError: + pass + return ipaddresses class RackSerializer(RalphAPISerializer): diff --git a/src/ralph/data_center/api/views.py b/src/ralph/data_center/api/views.py index c711a68e25..43379dc7da 100644 --- a/src/ralph/data_center/api/views.py +++ b/src/ralph/data_center/api/views.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- - +from django.db.models import Prefetch from ralph.api import RalphAPIViewSet from ralph.assets.api.filters import NetworkableObjectFilters from ralph.assets.api.views import BaseObjectViewSet, BaseObjectViewSetMixin +from ralph.assets.models import Ethernet from ralph.data_center.admin import DataCenterAssetAdmin from ralph.data_center.api.serializers import ( AccessorySerializer, @@ -47,11 +48,12 @@ class DataCenterAssetViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): 'property_of', 'budget_info' ] prefetch_related = BaseObjectViewSet.prefetch_related + [ - 'service_env__service__environments', - 'service_env__service__business_owners', - 'service_env__service__technical_owners', 'connections', 'tags', + Prefetch( + 'ethernet', + queryset=Ethernet.objects.select_related('ipaddress') + ), ] filter_fields = [ 'service_env__service__uid', diff --git a/src/ralph/data_center/tests/factories.py b/src/ralph/data_center/tests/factories.py index 0ff781dc31..0ccc39e7a7 100644 --- a/src/ralph/data_center/tests/factories.py +++ b/src/ralph/data_center/tests/factories.py @@ -10,6 +10,8 @@ BudgetInfoFactory, ConfigurationClassFactory, DataCenterAssetModelFactory, + EthernetFactory, + EthernetWithIPAddressFactory, ServiceEnvironmentFactory ) from ralph.data_center.models.physical import ( @@ -115,6 +117,44 @@ class Meta: model = DataCenterAsset +class DataCenterAssetFullFactory(DataCenterAssetFactory): + """ + Factory for DataCenterAsset and m2m relations + """ + rack = factory.SubFactory(RackFactory) + + # m2m relations + # TODO: parent, networks (as terminators), operations, security scans, + # clusters, tags + eth1 = factory.RelatedFactory( + EthernetWithIPAddressFactory, + 'base_object', + ipaddress__is_management=True, + ) + eth2 = factory.RelatedFactory(EthernetFactory, 'base_object') + eth3 = factory.RelatedFactory( + EthernetWithIPAddressFactory, + 'base_object', + ipaddress__dhcp_expose=True, + ) + licence1 = factory.RelatedFactory( + 'ralph.licences.tests.factories.BaseObjectLicenceFactory', 'base_object' + ) + licence2 = factory.RelatedFactory( + 'ralph.licences.tests.factories.BaseObjectLicenceFactory', + 'base_object', + quantity=3 + ) + support1 = factory.RelatedFactory( + 'ralph.supports.tests.factories.BaseObjectsSupportFactory', + 'baseobject' + ) + support2 = factory.RelatedFactory( + 'ralph.supports.tests.factories.BaseObjectsSupportFactory', + 'baseobject' + ) + + class DatabaseFactory(DjangoModelFactory): service_env = factory.SubFactory(ServiceEnvironmentFactory) diff --git a/src/ralph/data_center/tests/test_api.py b/src/ralph/data_center/tests/test_api.py index 65c15344b0..0ade3aae0b 100644 --- a/src/ralph/data_center/tests/test_api.py +++ b/src/ralph/data_center/tests/test_api.py @@ -22,6 +22,7 @@ ClusterFactory, ClusterTypeFactory, DataCenterAssetFactory, + DataCenterAssetFullFactory, RackAccessoryFactory, RackFactory, ServerRoomFactory @@ -35,7 +36,7 @@ def setUp(self): self.service_env = ServiceEnvironmentFactory() self.model = DataCenterAssetModelFactory() self.rack = RackFactory() - self.dc_asset = DataCenterAssetFactory( + self.dc_asset = DataCenterAssetFullFactory( rack=self.rack, position=10, model=self.model, @@ -44,11 +45,12 @@ def setUp(self): ethernet=EthernetFactory(base_object=self.dc_asset) ) self.dc_asset.tags.add('db', 'test') - self.dc_asset_2 = DataCenterAssetFactory() + self.dc_asset_2 = DataCenterAssetFullFactory() def test_get_data_center_assets_list(self): url = reverse('datacenterasset-list') - response = self.client.get(url, format='json') + with self.assertNumQueries(9): + response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response.data['count'], DataCenterAsset.objects.count() @@ -65,6 +67,11 @@ def test_get_data_center_asset_details(self): self.assertEqual( response.data['model']['id'], self.dc_asset.model.id ) + self.assertEqual(len(response.data['ethernet']), 4) + self.assertIn(self.ip.address, [ + eth['ipaddress']['address'] for eth in response.data['ethernet'] + if eth['ipaddress'] + ]) def test_create_data_center_asset(self): url = reverse('datacenterasset-list') diff --git a/src/ralph/data_center/tests/test_models.py b/src/ralph/data_center/tests/test_models.py index 03e7cd81fa..4168447393 100644 --- a/src/ralph/data_center/tests/test_models.py +++ b/src/ralph/data_center/tests/test_models.py @@ -225,17 +225,6 @@ def test_network_environment(self): self._prepare_rack(self.dc_asset, '192.168.1.0/24') self.assertEqual(self.dc_asset.network_environment, self.net_env) - def test_network_environment_many_options(self): - self._prepare_rack(self.dc_asset, '192.168.1.0/24') - proper_net_env = self.net_env - self._prepare_rack(self.dc_asset, '192.168.2.0/24', rack=self.rack) - self.assertGreater( - NetworkEnvironment.objects.filter(network__racks=self.rack).count(), - 1 - ) - self.assertEqual(self.dc_asset.network_environment, proper_net_env) - self.assertNotEqual(self.dc_asset.network_environment, self.net_env) - # ========================================================================= # next free hostname # ========================================================================= diff --git a/src/ralph/licences/api.py b/src/ralph/licences/api.py index f86328070f..5e61db2423 100644 --- a/src/ralph/licences/api.py +++ b/src/ralph/licences/api.py @@ -68,7 +68,9 @@ class BaseObjectLicenceViewSet(RalphAPIViewSet): serializer_class = BaseObjectLicenceSerializer select_related = [ 'licence', 'licence__region', 'licence__manufacturer', - 'licence__licence_type', 'licence__software', 'base_object' + 'licence__licence_type', 'licence__software', 'base_object', + 'licence__budget_info', 'licence__office_infrastructure', + 'licence__property_of', ] save_serializer_class = BaseObjectLicenceSerializer diff --git a/src/ralph/licences/api_simple.py b/src/ralph/licences/api_simple.py index 5b8965d175..102185241d 100644 --- a/src/ralph/licences/api_simple.py +++ b/src/ralph/licences/api_simple.py @@ -10,7 +10,10 @@ class SimpleLicenceSerializer(RalphAPISerializer): class Meta: model = Licence depth = 1 - exclude = ('base_objects', 'users', 'content_type', 'service_env') + exclude = ( + 'base_objects', 'users', 'content_type', 'service_env', 'parent', + 'configuration_path', 'tags' + ) class SimpleLicenceUserSerializer(RalphAPISerializer): diff --git a/src/ralph/licences/models.py b/src/ralph/licences/models.py index 2d222a0ae1..ea5a803ddd 100644 --- a/src/ralph/licences/models.py +++ b/src/ralph/licences/models.py @@ -231,6 +231,15 @@ class Licence(Regionalizable, AdminAbsoluteUrlMixin, BaseObject): objects_used_free_with_related = LicencesUsedFreeRelatedObjectsManager() def __str__(self): + return "{} x {} - {} ({})".format( + self.number_bought, + self.software.name, + self.invoice_date, + self.niw, + ) + + @cached_property + def autocomplete_str(self): return "{} ({} free) x {} - {} ({})".format( self.number_bought, self.free, diff --git a/src/ralph/networks/api.py b/src/ralph/networks/api.py index b0765156e2..7d71c817ad 100644 --- a/src/ralph/networks/api.py +++ b/src/ralph/networks/api.py @@ -41,8 +41,8 @@ class Meta: class IPAddressSerializer(RalphAPISerializer): - ethernet = EthernetSerializer() network = NetworkSimpleSerializer() + ethernet = EthernetSerializer() class Meta: model = IPAddress @@ -80,7 +80,7 @@ class IPAddressViewSet(RalphAPIViewSet): ] filter_fields = [ 'hostname', 'ethernet__base_object', 'network', 'network__address', - 'status', 'is_public', 'is_management', 'dhcp_expose' + 'status', 'is_public', 'is_management', 'dhcp_expose', 'ethernet__mac', ] def destroy(self, request, *args, **kwargs): diff --git a/src/ralph/networks/api_simple.py b/src/ralph/networks/api_simple.py new file mode 100644 index 0000000000..76e51ab85e --- /dev/null +++ b/src/ralph/networks/api_simple.py @@ -0,0 +1,10 @@ +from ralph.api import RalphAPISerializer +from ralph.networks.models import IPAddress + + +class IPAddressSimpleSerializer(RalphAPISerializer): + class Meta: + model = IPAddress + fields = ( + 'id', 'address', 'hostname', 'dhcp_expose', 'is_management', 'url' + ) diff --git a/src/ralph/networks/tests/factories.py b/src/ralph/networks/tests/factories.py index a7d651867e..812eef4f65 100644 --- a/src/ralph/networks/tests/factories.py +++ b/src/ralph/networks/tests/factories.py @@ -1,3 +1,5 @@ +import ipaddress + import factory from factory.django import DjangoModelFactory from factory.fuzzy import FuzzyText @@ -12,7 +14,7 @@ class NetworkEnvironmentFactory(DjangoModelFactory): - name = factory.Iterator(['DC1', 'DC2', 'Warehouse']) + name = factory.Iterator(['prod1', 'test1', 'preprod1', 'dev1']) data_center = factory.SubFactory(DataCenterFactory) hostname_template_prefix = 's1' hostname_template_postfix = '.mydc.net' @@ -41,6 +43,30 @@ class Meta: model = IPAddress +def _get_network_address(ip, mask=24): + """ + Return address of the IPv4 network for single IPv4 address with given mask. + """ + ip = ipaddress.ip_address(ip) + return ipaddress.ip_network( + '{}/{}'.format( + ipaddress.ip_address(int(ip) & (2**32 - 1) << (32 - mask)), + mask + ) + ) + + +class IPAddressWithNetworkFactory(IPAddressFactory): + network = factory.SubFactory( + NetworkFactory, + address=factory.LazyAttribute( + lambda ipaddress: _get_network_address( + ipaddress.factory_parent.address + ) + ) + ) + + class ManagementIPAddressFactory(DjangoModelFactory): is_management = True diff --git a/src/ralph/networks/tests/test_api.py b/src/ralph/networks/tests/test_api.py index 7128966101..b1b9bfe278 100644 --- a/src/ralph/networks/tests/test_api.py +++ b/src/ralph/networks/tests/test_api.py @@ -31,6 +31,29 @@ def test_get_ip_list_filter_by_dhcp_expose(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 1) + def test_get_ip_list_filter_by_mac(self): + url = '{}?ethernet__mac={}'.format( + reverse('ipaddress-list'), self.ip1.ethernet.mac + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + + def test_get_ip_details(self): + url = reverse('ipaddress-detail', args=(self.ip1.id,)) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['ethernet']['mac'], self.ip1.ethernet.mac + ) + self.assertEqual( + response.data['ethernet']['ipaddress']['address'], self.ip1.address + ) + self.assertEqual( + response.data['ethernet']['base_object']['id'], + self.ip1.ethernet.base_object.id + ) + def test_change_network_should_not_pass(self): net = NetworkFactory(address='192.168.1.0/24') data = {'network': net.id} diff --git a/src/ralph/virtual/admin.py b/src/ralph/virtual/admin.py index fa6e55ccca..58501d67cd 100644 --- a/src/ralph/virtual/admin.py +++ b/src/ralph/virtual/admin.py @@ -4,7 +4,11 @@ from django.utils.translation import ugettext_lazy as _ from ralph.admin import RalphAdmin, RalphAdminForm, RalphTabularInline, register -from ralph.admin.filters import IPFilter, TagsListFilter +from ralph.admin.filters import ( + IPFilter, + TagsListFilter, + TreeRelatedAutocompleteFilterWithDescendants +) from ralph.assets.models.components import Ethernet from ralph.data_center.models.virtual import BaseObjectCluster from ralph.lib.transitions.admin import TransitionAdminMixin @@ -41,8 +45,9 @@ class VirtualServerAdmin(TransitionAdminMixin, RalphAdmin): form = VirtualServerForm search_fields = ['hostname', 'sn'] list_filter = [ - 'sn', 'hostname', 'service_env', 'configuration_path', IPFilter, - 'parent', ('tags', TagsListFilter) + 'sn', 'hostname', 'service_env', IPFilter, + 'parent', ('tags', TagsListFilter), + ('configuration_path__module', TreeRelatedAutocompleteFilterWithDescendants) # noqa ] list_display = [ 'hostname', 'type', 'sn', 'service_env', 'configuration_path' @@ -54,7 +59,7 @@ class VirtualServerAdmin(TransitionAdminMixin, RalphAdmin): ] list_select_related = [ 'service_env__service', 'service_env__environment', 'type', - 'configuration_path' + 'configuration_path__module' ] change_views = [VirtualServerNetworkView] diff --git a/src/ralph/virtual/api.py b/src/ralph/virtual/api.py index 96a2232cde..bb93e038e2 100644 --- a/src/ralph/virtual/api.py +++ b/src/ralph/virtual/api.py @@ -1,18 +1,23 @@ # -*- coding: utf-8 -*- from django.db import transaction +from django.db.models import Prefetch from rest_framework import relations, serializers from ralph.api import RalphAPISerializer, RalphAPIViewSet, router from ralph.api.serializers import RalphAPISaveSerializer +from ralph.assets.api.filters import NetworkableObjectFilters from ralph.assets.api.serializers import ( BaseObjectSerializer, ServiceEnvironmentSimpleSerializer ) +from ralph.assets.api.views import BaseObjectViewSet, BaseObjectViewSetMixin +from ralph.assets.models import Ethernet from ralph.data_center.api.serializers import ( - ClusterSimpleSerializer, + ComponentSerializerMixin, DataCenterAssetSimpleSerializer ) from ralph.data_center.models import DataCenterAsset +from ralph.virtual.admin import VirtualServerAdmin from ralph.virtual.models import ( CloudFlavor, CloudHost, @@ -123,14 +128,16 @@ class Meta: model = VirtualServerType -class VirtualServerSerializer(BaseObjectSerializer): +# TODO: select related +class VirtualServerSerializer(ComponentSerializerMixin, BaseObjectSerializer): type = VirtualServerTypeSerializer() # TODO: cast BaseObject to DataCenterAsset for hypervisor field hypervisor = DataCenterAssetSimpleSerializer(source='parent') - cluster = ClusterSimpleSerializer() + # TODO: clusters class Meta(BaseObjectSerializer.Meta): model = VirtualServer + exclude = ('content_type', 'cluster') class VirtualServerSaveSerializer(RalphAPISaveSerializer): @@ -181,14 +188,33 @@ class VirtualServerTypeViewSet(RalphAPIViewSet): serializer_class = VirtualServerTypeSerializer -class VirtualServerViewSet(RalphAPIViewSet): +class VirtualServerFilterSet(NetworkableObjectFilters): + class Meta(NetworkableObjectFilters.Meta): + model = VirtualServer + + +class VirtualServerViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): queryset = VirtualServer.objects.all() serializer_class = VirtualServerSerializer save_serializer_class = VirtualServerSaveSerializer - select_related = [ - 'parent', 'service_env__service', 'service_env__environment', 'cluster' + select_related = VirtualServerAdmin.list_select_related + [ + 'parent', 'service_env__service', 'service_env__environment', + 'configuration_path', + ] + prefetch_related = BaseObjectViewSet.prefetch_related + [ + 'tags', + Prefetch( + 'ethernet', + queryset=Ethernet.objects.select_related('ipaddress') + ), + # TODO: clusters + ] + filter_fields = [ + 'service_env__service__uid', + 'service_env__service__name', + 'service_env__service__id', ] - prefetch_related = ['tags', 'licences'] + additional_filter_class = VirtualServerFilterSet router.register(r'cloud-flavors', CloudFlavorViewSet) diff --git a/src/ralph/virtual/models.py b/src/ralph/virtual/models.py index 8284973027..7be9650972 100644 --- a/src/ralph/virtual/models.py +++ b/src/ralph/virtual/models.py @@ -252,6 +252,7 @@ class VirtualServer(AdminAbsoluteUrlMixin, NetworkableBaseObject, BaseObject): verbose_name=_('SN'), unique=True, ) + # TODO: remove this field cluster = models.ForeignKey(Cluster, blank=True, null=True) @cached_property diff --git a/src/ralph/virtual/tests/factories.py b/src/ralph/virtual/tests/factories.py index 988906e27d..5c220bcd0a 100644 --- a/src/ralph/virtual/tests/factories.py +++ b/src/ralph/virtual/tests/factories.py @@ -1,7 +1,12 @@ import factory from factory.django import DjangoModelFactory -from ralph.assets.tests.factories import ServiceEnvironmentFactory +from ralph.assets.tests.factories import ( + ConfigurationClassFactory, + EthernetFactory, + EthernetWithIPAddressFactory, + ServiceEnvironmentFactory +) from ralph.data_center.tests.factories import DataCenterAssetFactory from ralph.virtual.models import ( CloudFlavor, @@ -81,6 +86,24 @@ class VirtualServerFactory(DjangoModelFactory): type = factory.SubFactory(VirtualServerTypeFactory) sn = factory.Faker('ssn') parent = factory.SubFactory(DataCenterAssetFactory) + configuration_path = factory.SubFactory(ConfigurationClassFactory) class Meta: model = VirtualServer + + +class VirtualServerFullFactory(VirtualServerFactory): + eth1 = factory.RelatedFactory(EthernetFactory, 'base_object') + eth2 = factory.RelatedFactory( + EthernetWithIPAddressFactory, + 'base_object', + ipaddress__dhcp_expose=True, + ) + licence1 = factory.RelatedFactory( + 'ralph.licences.tests.factories.BaseObjectLicenceFactory', 'base_object' + ) + licence2 = factory.RelatedFactory( + 'ralph.licences.tests.factories.BaseObjectLicenceFactory', + 'base_object', + quantity=3 + ) diff --git a/src/ralph/virtual/tests/test_api.py b/src/ralph/virtual/tests/test_api.py index 114dbc03b3..9c5fb17cac 100644 --- a/src/ralph/virtual/tests/test_api.py +++ b/src/ralph/virtual/tests/test_api.py @@ -6,11 +6,16 @@ from ralph.assets.models.assets import ServiceEnvironment from ralph.assets.models.choices import ComponentType from ralph.assets.models.components import ComponentModel -from ralph.assets.tests.factories import EnvironmentFactory, ServiceFactory +from ralph.assets.tests.factories import ( + EnvironmentFactory, + EthernetFactory, + ServiceFactory +) from ralph.data_center.tests.factories import ( ClusterFactory, DataCenterAssetFactory ) +from ralph.networks.tests.factories import IPAddressFactory from ralph.virtual.models import ( CloudFlavor, CloudHost, @@ -25,8 +30,7 @@ CloudHostFactory, CloudProjectFactory, CloudProviderFactory, - VirtualServerFactory, - VirtualServerTypeFactory + VirtualServerFullFactory ) @@ -294,21 +298,18 @@ def setUp(self): self.hypervisor = DataCenterAssetFactory() self.cluster = ClusterFactory() self.type = VirtualServerType.objects.create(name='XEN') - self.virtual_server = VirtualServerFactory(cluster=self.cluster) + self.virtual_server = VirtualServerFullFactory() + self.virtual_server2 = VirtualServerFullFactory() + self.ip = IPAddressFactory( + ethernet=EthernetFactory(base_object=self.virtual_server2) + ) def test_get_virtual_server_list(self): url = reverse('virtualserver-list') - response = self.client.get(url, format='json') + with self.assertNumQueries(8): + response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['count'], 1) - self.assertEqual( - response.data['results'][0]['hostname'], - self.virtual_server.hostname - ) - self.assertEqual( - response.data['results'][0]['cluster']['id'], - self.cluster.id - ) + self.assertEqual(response.data['count'], 2) def test_get_virtual_server_details(self): url = reverse('virtualserver-detail', args=(self.virtual_server.id,)) @@ -318,28 +319,34 @@ def test_get_virtual_server_details(self): response.data['hostname'], self.virtual_server.hostname ) - self.assertEqual( - response.data['cluster']['id'], - self.cluster.id + self.assertEqual(len(response.data['ethernet']), 2) + self.assertCountEqual( + [ + eth['ipaddress']['address'] + for eth in response.data['ethernet'] + if eth['ipaddress'] + ], + self.virtual_server.ipaddresses.values_list('address', flat=True) ) def test_create_virtual_server(self): + virtual_server_count = VirtualServer.objects.count() url = reverse('virtualserver-list') data = { 'hostname': 's1234.local', 'type': self.type.id, 'sn': '143ed36a-3e86-457d-9e19-3dcfe4d5ed26', 'hypervisor': self.hypervisor.id, - 'cluster': self.cluster.id, } response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(VirtualServer.objects.count(), 2) + self.assertEqual( + VirtualServer.objects.count(), virtual_server_count + 1 + ) virtual_server = VirtualServer.objects.get(pk=response.data['id']) self.assertEqual(virtual_server.hostname, data['hostname']) self.assertEqual(virtual_server.parent.id, self.hypervisor.id) self.assertEqual(virtual_server.sn, data['sn']) - self.assertEqual(virtual_server.cluster, self.cluster) def test_patch_virtual_server(self): url = reverse('virtualserver-detail', args=(self.virtual_server.id,)) @@ -350,3 +357,92 @@ def test_patch_virtual_server(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.virtual_server.refresh_from_db() self.assertEqual(self.virtual_server.hostname, 's111111.local') + + def test_filter_by_configuration_path(self): + url = reverse('virtualserver-list') + '?configuration_path={}'.format( + self.virtual_server.configuration_path.path, + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_hostname(self): + url = reverse('virtualserver-list') + '?hostname={}'.format( + self.virtual_server.hostname, + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_ip_address(self): + url = reverse('virtualserver-list') + '?ip={}'.format( + self.ip.address, + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_service_uid(self): + url = reverse('virtualserver-list') + '?service={}'.format( + self.virtual_server.service_env.service.uid, + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_service_uid2(self): + url = ( + reverse('virtualserver-list') + + '?service_env__service__uid={}'.format( + self.virtual_server.service_env.service.uid, + ) + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_service_id(self): + url = ( + reverse('virtualserver-list') + + '?service_env__service__id={}'.format( + self.virtual_server.service_env.service.id, + ) + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_service_name(self): + url = reverse('virtualserver-list') + '?service={}'.format( + self.virtual_server.service_env.service.name, + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + ) + + def test_filter_by_service_name2(self): + url = ( + reverse('virtualserver-list') + + '?service_env__service__name={}'.format( + self.virtual_server.service_env.service.name, + ) + ) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['count'], 1 + )