diff --git a/proto/context.proto b/proto/context.proto index d23a46a480f991dae322dc613a7190609bed4751..7f99c4525d8ce2e7c3d4e3b4b2f82fed6d861e96 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -68,11 +68,11 @@ service ContextService { rpc GetSliceEvents (Empty ) returns (stream SliceEvent ) {} rpc SelectSlice (SliceFilter ) returns ( SliceList ) {} - rpc CreateQoSProfile (QoSProfile ) returns (QoSProfile ) {} - rpc UpdateQoSProfile (QoSProfile ) returns (QoSProfile ) {} - rpc DeleteQoSProfile (QoSProfileId ) returns (Empty ) {} - rpc GetQoSProfile (QoSProfileId ) returns (QoSProfile ) {} - rpc GetQoSProfiles (Empty ) returns (stream QoSProfile) {} + rpc CreateQoSProfile (QoSProfile ) returns ( QoSProfile ) {} + rpc UpdateQoSProfile (QoSProfile ) returns ( QoSProfile ) {} + rpc DeleteQoSProfile (QoSProfileId ) returns ( Empty ) {} + rpc GetQoSProfile (QoSProfileId ) returns ( QoSProfile ) {} + rpc GetQoSProfiles (Empty ) returns (stream QoSProfile ) {} rpc ListConnectionIds (ServiceId ) returns ( ConnectionIdList) {} rpc ListConnections (ServiceId ) returns ( ConnectionList ) {} @@ -419,6 +419,12 @@ message QoSProfileValueUnitPair { string unit = 2; } +message QoDConstraintsRequest { + QoSProfileId qos_profile_id = 1; + double start_timestamp = 2; + float duration = 3; +} + message QoSProfile { QoSProfileId qos_profile_id = 1; string name = 2; @@ -571,7 +577,7 @@ message Constraint_Custom { } message Constraint_Schedule { - float start_timestamp = 1; + double start_timestamp = 1; float duration_days = 2; } @@ -634,6 +640,22 @@ message Constraint_Exclusions { repeated LinkId link_ids = 4; } +message Constraint_QoSProfile { + QoSProfileValueUnitPair target_min_upstream_rate = 1; + QoSProfileValueUnitPair max_upstream_rate = 2; + QoSProfileValueUnitPair max_upstream_burst_rate = 3; + QoSProfileValueUnitPair target_min_downstream_rate = 4; + QoSProfileValueUnitPair max_downstream_rate = 5; + QoSProfileValueUnitPair max_downstream_burst_rate = 6; + QoSProfileValueUnitPair min_duration = 7; + QoSProfileValueUnitPair max_duration = 8; + int32 priority = 9; + QoSProfileValueUnitPair packet_delay_budget = 10; + QoSProfileValueUnitPair jitter = 11; + int32 packet_error_loss_rate = 12; + +} + message Constraint { ConstraintActionEnum action = 1; oneof constraint { @@ -646,6 +668,7 @@ message Constraint { Constraint_SLA_Availability sla_availability = 8; Constraint_SLA_Isolation_level sla_isolation = 9; Constraint_Exclusions exclusions = 10; + Constraint_QoSProfile qos_profile = 11; } } diff --git a/proto/qos_profile.proto b/proto/qos_profile.proto index 8e1fc80a3790adba985f39d5640681b26d33cfd3..557cef5391077043e3367054768ce87ef5f32d97 100644 --- a/proto/qos_profile.proto +++ b/proto/qos_profile.proto @@ -18,9 +18,10 @@ package qos_profile; import "context.proto"; service QoSProfileService { - rpc CreateQoSProfile (context.QoSProfile ) returns (context.QoSProfile ) {} - rpc UpdateQoSProfile (context.QoSProfile ) returns (context.QoSProfile ) {} - rpc DeleteQoSProfile (context.QoSProfileId) returns (context.Empty ) {} - rpc GetQoSProfile (context.QoSProfileId) returns (context.QoSProfile ) {} - rpc GetQoSProfiles (context.Empty ) returns (stream context.QoSProfile) {} + rpc CreateQoSProfile (context.QoSProfile ) returns (context.QoSProfile ) {} + rpc UpdateQoSProfile (context.QoSProfile ) returns (context.QoSProfile ) {} + rpc DeleteQoSProfile (context.QoSProfileId ) returns (context.Empty ) {} + rpc GetQoSProfile (context.QoSProfileId ) returns (context.QoSProfile ) {} + rpc GetQoSProfiles (context.Empty ) returns (stream context.QoSProfile ) {} + rpc GetConstraintListFromQoSProfile (context.QoDConstraintsRequest) returns (stream context.Constraint ) {} } diff --git a/src/qos_profile/client/QoSProfileClient.py b/src/qos_profile/client/QoSProfileClient.py index 7a857ec25ed103a3bcb1c15dab04f3af19868884..e7bec8739ca1b374d79694c14b3c6fa511d7079a 100644 --- a/src/qos_profile/client/QoSProfileClient.py +++ b/src/qos_profile/client/QoSProfileClient.py @@ -16,7 +16,7 @@ from typing import Iterator import grpc, logging from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc -from common.proto.context_pb2 import Empty, QoSProfileId, QoSProfile +from common.proto.context_pb2 import Empty, QoSProfileId, QoSProfile, QoDConstraintsRequest, Constraint from common.proto.qos_profile_pb2_grpc import QoSProfileServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string @@ -80,3 +80,10 @@ class QoSProfileClient: response = self.stub.GetQoSProfiles(request) LOGGER.debug('GetQoSProfiles result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def GetConstraintListFromQoSProfile(self, request: QoDConstraintsRequest) -> Iterator[Constraint]: + LOGGER.debug('GetConstraintListFromQoSProfile request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetConstraintListFromQoSProfile(request) + LOGGER.debug('GetConstraintListFromQoSProfile result: {:s}'.format(grpc_message_to_json_string(response))) + return response \ No newline at end of file diff --git a/src/qos_profile/service/QoSProfileServiceServicerImpl.py b/src/qos_profile/service/QoSProfileServiceServicerImpl.py index cabca909da9a6ecea1ceccb5ffcc0c008053e603..bdcc3e8c3c17544b1c8e05ecbf64d6107e208b2b 100644 --- a/src/qos_profile/service/QoSProfileServiceServicerImpl.py +++ b/src/qos_profile/service/QoSProfileServiceServicerImpl.py @@ -17,7 +17,7 @@ from typing import Iterator import grpc._channel from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method -from common.proto.context_pb2 import Empty, QoSProfileId, QoSProfile +from common.proto.context_pb2 import QoDConstraintsRequest, Constraint, ConstraintActionEnum, Constraint_QoSProfile, Constraint_Schedule, Empty, QoSProfileId, QoSProfile from common.proto.qos_profile_pb2_grpc import QoSProfileServiceServicer from context.client.ContextClient import ContextClient @@ -87,3 +87,36 @@ class QoSProfileServiceServicerImpl(QoSProfileServiceServicer): context_client = ContextClient() yield from context_client.GetQoSProfiles(request) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetConstraintListFromQoSProfile(self, request: QoDConstraintsRequest, context: grpc.ServicerContext) -> Iterator[Constraint]: + context_client = ContextClient() + try: + qos_profile = context_client.GetQoSProfile(request.qos_profile_id) + except grpc._channel._InactiveRpcError as exc: + if exc.code() == grpc.StatusCode.NOT_FOUND: + context.set_details(f'QoSProfile {request.qos_profile_id.qos_profile_id.uuid} not found') + context.set_code(grpc.StatusCode.NOT_FOUND) + yield Constraint() + + qos_profile_constraint = Constraint_QoSProfile() + qos_profile_constraint.target_min_upstream_rate.CopyFrom(qos_profile.targetMinUpstreamRate) + qos_profile_constraint.max_upstream_rate.CopyFrom(qos_profile.maxUpstreamRate) + qos_profile_constraint.max_upstream_burst_rate.CopyFrom(qos_profile.maxUpstreamBurstRate) + qos_profile_constraint.target_min_downstream_rate.CopyFrom(qos_profile.targetMinDownstreamRate) + qos_profile_constraint.max_downstream_rate.CopyFrom(qos_profile.maxDownstreamRate) + qos_profile_constraint.max_downstream_burst_rate.CopyFrom(qos_profile.maxDownstreamBurstRate) + qos_profile_constraint.min_duration.CopyFrom(qos_profile.minDuration) + qos_profile_constraint.max_duration.CopyFrom(qos_profile.maxDuration) + qos_profile_constraint.priority = qos_profile.priority + qos_profile_constraint.packet_delay_budget.CopyFrom(qos_profile.packetDelayBudget) + qos_profile_constraint.jitter.CopyFrom(qos_profile.jitter) + qos_profile_constraint.packet_error_loss_rate =qos_profile.packetErrorLossRate + constraint_qos = Constraint() + constraint_qos.action = ConstraintActionEnum.CONSTRAINTACTION_SET + constraint_qos.qos_profile.CopyFrom(qos_profile_constraint) + yield constraint_qos + constraint_schedule = Constraint() + constraint_schedule.action = ConstraintActionEnum.CONSTRAINTACTION_SET + constraint_schedule.schedule.CopyFrom(Constraint_Schedule(start_timestamp=request.start_timestamp, duration_days=request.duration/86400)) + yield constraint_schedule diff --git a/src/qos_profile/tests/conftest.py b/src/qos_profile/tests/conftest.py index 37110c0b60aaf76655b44a8a79fb35aff77a8745..8d8e455f2ac1f72faf3e34835c860fa69e9a9417 100644 --- a/src/qos_profile/tests/conftest.py +++ b/src/qos_profile/tests/conftest.py @@ -14,6 +14,7 @@ import pytest from qos_profile.client.QoSProfileClient import QoSProfileClient +from common.proto.context_pb2 import Uuid, QoSProfileValueUnitPair, QoSProfileId, QoSProfile @pytest.fixture(scope='function') def qos_profile_client(): @@ -21,3 +22,26 @@ def qos_profile_client(): yield _client _client.close() + + +def create_qos_profile_from_json(qos_profile_data: dict) -> QoSProfile: + def create_QoSProfileValueUnitPair(data) -> QoSProfileValueUnitPair: + return QoSProfileValueUnitPair(value=data['value'], unit=data['unit']) + qos_profile = QoSProfile() + qos_profile.qos_profile_id.CopyFrom(QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_data['qos_profile_id']))) + qos_profile.name = qos_profile_data['name'] + qos_profile.description = qos_profile_data['description'] + qos_profile.status = qos_profile_data['status'] + qos_profile.targetMinUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinUpstreamRate'])) + qos_profile.maxUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamRate'])) + qos_profile.maxUpstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamBurstRate'])) + qos_profile.targetMinDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinDownstreamRate'])) + qos_profile.maxDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamRate'])) + qos_profile.maxDownstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamBurstRate'])) + qos_profile.minDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['minDuration'])) + qos_profile.maxDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDuration'])) + qos_profile.priority = qos_profile_data['priority'] + qos_profile.packetDelayBudget.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['packetDelayBudget'])) + qos_profile.jitter.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['jitter'])) + qos_profile.packetErrorLossRate = qos_profile_data['packetErrorLossRate'] + return qos_profile \ No newline at end of file diff --git a/src/qos_profile/tests/test_constraints.py b/src/qos_profile/tests/test_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..51f160fc5a21fa3f914872f356825a08e653a6b5 --- /dev/null +++ b/src/qos_profile/tests/test_constraints.py @@ -0,0 +1,142 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# 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. + +import logging +from google.protobuf.json_format import MessageToDict + +from common.proto.context_pb2 import QoDConstraintsRequest +from common.tools.grpc.Tools import grpc_message_to_json_string +from qos_profile.client.QoSProfileClient import QoSProfileClient + +from .conftest import create_qos_profile_from_json + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +qos_profile_data = { + "qos_profile_id": "0afc905f-f1f0-4ae2-9925-9df17140b8bf", + "name": "QCI_2_voice", + "description": "QoS profile for game streaming", + "status": "ACTIVE", + "targetMinUpstreamRate": { + "value": 5, + "unit": "bps" + }, + "maxUpstreamRate": { + "value": 5, + "unit": "bps" + }, + "maxUpstreamBurstRate": { + "value": 5, + "unit": "bps" + }, + "targetMinDownstreamRate": { + "value": 5, + "unit": "bps" + }, + "maxDownstreamRate": { + "value": 5, + "unit": "bps" + }, + "maxDownstreamBurstRate": { + "value": 5, + "unit": "bps" + }, + "minDuration": { + "value": 5, + "unit": "Minutes" + }, + "maxDuration": { + "value": 6, + "unit": "Minutes" + }, + "priority": 5, + "packetDelayBudget": { + "value": 5, + "unit": "Minutes" + }, + "jitter": { + "value": 5, + "unit": "Minutes" + }, + "packetErrorLossRate": 3 +} + +target_qos_profile_constraint = { + "action": "CONSTRAINTACTION_SET", + "qos_profile": { + "target_min_upstream_rate": { + "value": 5, + "unit": "bps" + }, + "max_upstream_rate": { + "value": 5, + "unit": "bps" + }, + "max_upstream_burst_rate": { + "value": 5, + "unit": "bps" + }, + "target_min_downstream_rate": { + "value": 5, + "unit": "bps" + }, + "max_downstream_rate": { + "value": 5, + "unit": "bps" + }, + "max_downstream_burst_rate": { + "value": 5, + "unit": "bps" + }, + "min_duration": { + "value": 5, + "unit": "Minutes" + }, + "max_duration": { + "value": 6, + "unit": "Minutes" + }, + "priority": 5, + "packet_delay_budget": { + "value": 5, + "unit": "Minutes" + }, + "jitter": { + "value": 5, + "unit": "Minutes" + }, + "packet_error_loss_rate": 3 + } +} + + +def test_get_constraints(qos_profile_client: QoSProfileClient): + qos_profile = create_qos_profile_from_json(qos_profile_data) + qos_profile_created = qos_profile_client.CreateQoSProfile(qos_profile) + LOGGER.info('qos_profile_data = {:s}'.format(grpc_message_to_json_string(qos_profile_created))) + constraints = list(qos_profile_client.GetConstraintListFromQoSProfile(QoDConstraintsRequest( + qos_profile_id=qos_profile.qos_profile_id, start_timestamp=1726063284.25332, duration=86400) + )) + constraint_1 = constraints[0] + constraint_2 = constraints[1] + assert len(constraints) == 2 + assert constraint_1.WhichOneof('constraint') == 'qos_profile' + print(MessageToDict(constraint_1, preserving_proto_field_name=True)) + assert MessageToDict(constraint_1, preserving_proto_field_name=True) == target_qos_profile_constraint + assert constraint_2.WhichOneof('constraint') == 'schedule' + assert constraint_2.schedule.start_timestamp == 1726063284.25332 + assert constraint_2.schedule.duration_days == 1 + + qos_profile_client.DeleteQoSProfile(qos_profile.qos_profile_id) \ No newline at end of file diff --git a/src/qos_profile/tests/test_crud.py b/src/qos_profile/tests/test_crud.py index 89bc87322a0fdec3c35c12996481eb8be85ca635..b98351ce9ae0d7da5151702d5b76c436b4844928 100644 --- a/src/qos_profile/tests/test_crud.py +++ b/src/qos_profile/tests/test_crud.py @@ -14,8 +14,8 @@ from grpc import RpcError, StatusCode import logging, pytest -from common.proto.context_pb2 import Empty, Uuid, QoSProfileValueUnitPair, QoSProfileId, QoSProfile - +from .conftest import create_qos_profile_from_json +from common.proto.context_pb2 import Empty, Uuid, QoSProfileId from common.tools.grpc.Tools import grpc_message_to_json_string from qos_profile.client.QoSProfileClient import QoSProfileClient @@ -71,28 +71,6 @@ qos_profile_data = { "packetErrorLossRate": 3 } -def create_qos_profile_from_json(qos_profile_data: dict) -> QoSProfile: - def create_QoSProfileValueUnitPair(data) -> QoSProfileValueUnitPair: - return QoSProfileValueUnitPair(value=data['value'], unit=data['unit']) - qos_profile = QoSProfile() - qos_profile.qos_profile_id.CopyFrom(QoSProfileId(qos_profile_id=Uuid(uuid=qos_profile_data['qos_profile_id']))) - qos_profile.name = qos_profile_data['name'] - qos_profile.description = qos_profile_data['description'] - qos_profile.status = qos_profile_data['status'] - qos_profile.targetMinUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinUpstreamRate'])) - qos_profile.maxUpstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamRate'])) - qos_profile.maxUpstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxUpstreamBurstRate'])) - qos_profile.targetMinDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['targetMinDownstreamRate'])) - qos_profile.maxDownstreamRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamRate'])) - qos_profile.maxDownstreamBurstRate.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDownstreamBurstRate'])) - qos_profile.minDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['minDuration'])) - qos_profile.maxDuration.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['maxDuration'])) - qos_profile.priority = qos_profile_data['priority'] - qos_profile.packetDelayBudget.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['packetDelayBudget'])) - qos_profile.jitter.CopyFrom(create_QoSProfileValueUnitPair(qos_profile_data['jitter'])) - qos_profile.packetErrorLossRate = qos_profile_data['packetErrorLossRate'] - return qos_profile - def test_create_qos_profile(qos_profile_client: QoSProfileClient): qos_profile = create_qos_profile_from_json(qos_profile_data) qos_profile_created = qos_profile_client.CreateQoSProfile(qos_profile)