Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions common/src/apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,29 @@ impl Default for ApplicationAttributes {
}
}

#[derive(Clone, Debug)]
pub struct SessionAttributes {
pub id: SessionID,
pub application: String,
pub slots: u32,
pub common_data: Option<CommonData>,
pub min_instances: u32,
pub max_instances: Option<u32>,
}

impl Default for SessionAttributes {
fn default() -> Self {
Self {
id: String::new(),
application: String::new(),
slots: 1,
common_data: None,
min_instances: 0,
max_instances: None,
}
}
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, strum_macros::Display)]
pub enum SessionState {
#[default]
Expand Down Expand Up @@ -164,6 +187,8 @@ pub struct Session {
pub events: Vec<Event>,

pub status: SessionStatus,
pub min_instances: u32, // Minimum number of instances
pub max_instances: Option<u32>, // Maximum number of instances (None means unlimited)
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, strum_macros::Display)]
Expand Down Expand Up @@ -559,6 +584,8 @@ impl Clone for Session {
completion_time: self.completion_time,
events: self.events.clone(),
status: self.status.clone(),
min_instances: self.min_instances,
max_instances: self.max_instances,
};

for (id, t) in &self.tasks {
Expand Down Expand Up @@ -772,6 +799,8 @@ impl From<&Session> for rpc::Session {
application: ssn.application.clone(),
slots: ssn.slots,
common_data: ssn.common_data.clone().map(CommonData::into),
min_instances: ssn.min_instances,
max_instances: ssn.max_instances,
}),
status: Some(status),
}
Expand Down
1,471 changes: 1,471 additions & 0 deletions docs/designs/RFE323-runner-v2/FS.md

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions e2e/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,105 @@ def test_runner_error_no_package_config():
pass

assert exc_info.value.code == flamepy.FlameErrorCode.INVALID_CONFIG


def test_runner_stateful_instance(check_package_config, check_flmrun_app):
"""Test Case 14: Test Runner with stateful=True for instance."""
with rl.Runner("test-runner-stateful") as rr:
# Create a Counter instance
counter = Counter()

# Create a stateful service (state should persist across tasks)
cnt_service = rr.service(counter, stateful=True, autoscale=False)

# Call methods
cnt_service.add(5).wait()
cnt_service.increment().wait()
result = cnt_service.get_count()

# Get the result
value = result.get()
assert value == 6, f"Expected 6, got {value}"


def test_runner_stateless_function(check_package_config, check_flmrun_app):
"""Test Case 15: Test Runner with stateless function (default behavior)."""
with rl.Runner("test-runner-stateless-func") as rr:
# Create a service with a function (stateless by default)
sum_service = rr.service(sum_func, stateful=False, autoscale=True)

# Call the function multiple times
results = [sum_service(i, i+1) for i in range(5)]
values = rr.get(results)

# Verify results
expected = [1, 3, 5, 7, 9]
assert values == expected, f"Expected {expected}, got {values}"


def test_runner_class_single_instance(check_package_config, check_flmrun_app):
"""Test Case 16: Test Runner with class and autoscale=False (single instance)."""
with rl.Runner("test-runner-class-single") as rr:
# Create a service with a class, single instance mode
calc_service = rr.service(Calculator, stateful=False, autoscale=False)

# Call methods
result1 = calc_service.add(10, 5)
result2 = calc_service.multiply(3, 4)

values = rr.get([result1, result2])
assert values == [15, 12], f"Expected [15, 12], got {values}"


def test_runner_error_stateful_class(check_package_config, check_flmrun_app):
"""Test Case 17: Test that stateful=True raises error for class."""
with rl.Runner("test-runner-stateful-class-error") as rr:
# Trying to create a stateful service with a class should raise ValueError
with pytest.raises(ValueError) as exc_info:
rr.service(Counter, stateful=True)

assert "Cannot set stateful=True for a class" in str(exc_info.value)


def test_runner_defaults_function(check_package_config, check_flmrun_app):
"""Test Case 18: Test default parameters for function (stateful=False, autoscale=True)."""
with rl.Runner("test-runner-defaults-func") as rr:
# Create service with defaults (should be stateful=False, autoscale=True)
sum_service = rr.service(sum_func)

# Verify it works (defaults should be applied automatically)
result = sum_service(100, 200)
value = result.get()
assert value == 300, f"Expected 300, got {value}"


def test_runner_defaults_class(check_package_config, check_flmrun_app):
"""Test Case 19: Test default parameters for class (stateful=False, autoscale=False)."""
with rl.Runner("test-runner-defaults-class") as rr:
# Create service with class using defaults (should be stateful=False, autoscale=False)
counter_service = rr.service(Counter)

# Call methods
counter_service.add(10).wait()
counter_service.increment().wait()
result = counter_service.get_count()

value = result.get()
assert value == 11, f"Expected 11, got {value}"


def test_runner_defaults_instance(check_package_config, check_flmrun_app):
"""Test Case 20: Test default parameters for instance (stateful=False, autoscale=False)."""
with rl.Runner("test-runner-defaults-instance") as rr:
# Create an instance
calc = Calculator()

# Create service with instance using defaults (should be stateful=False, autoscale=False)
calc_service = rr.service(calc)

# Call methods
result1 = calc_service.add(5, 3)
result2 = calc_service.subtract(10, 4)

values = rr.get([result1, result2])
assert values == [8, 6], f"Expected [8, 6], got {values}"
2 changes: 2 additions & 0 deletions examples/pi/rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
application: app,
slots,
common_data: None,
min_instances: 0, // Default: no minimum guarantee
max_instances: None, // Default: unlimited
})
.await?;

Expand Down
2 changes: 2 additions & 0 deletions flmctl/src/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub async fn run(ctx: &FlameContext, app: &str, slots: &u32) -> Result<(), Box<d
application: app.to_owned(),
slots: *slots,
common_data: None,
min_instances: 0, // Default: no minimum guarantee
max_instances: None, // Default: unlimited (will use application's max_instances as fallback)
};

let ssn = conn.create_session(&attr).await?;
Expand Down
2 changes: 2 additions & 0 deletions flmexec/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
application: DEFAULT_APP.to_string(),
slots,
common_data: None,
min_instances: 0, // Default: no minimum guarantee
max_instances: None, // Default: unlimited
};
let ssn = conn.create_session(&ssn_attr).await?;
let ssn_creation_end_time = Local::now();
Expand Down
2 changes: 2 additions & 0 deletions flmping/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
application: DEFAULT_APP.to_string(),
slots,
common_data: cli.common_data.map(|s| s.into()),
min_instances: 0, // Default: no minimum guarantee
max_instances: None, // Default: unlimited
};
let ssn = conn.create_session(&ssn_attr).await?;
let ssn_creation_end_time = Instant::now();
Expand Down
2 changes: 2 additions & 0 deletions rpc/protos/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ message SessionSpec {
string application = 2;
uint32 slots = 3;
optional bytes common_data = 4;
uint32 min_instances = 5; // Minimum number of instances (default: 0)
optional uint32 max_instances = 6; // Maximum number of instances (null means unlimited)
}

message Session {
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/protos/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ message SessionSpec {
string application = 2;
uint32 slots = 3;
optional bytes common_data = 4;
uint32 min_instances = 5; // Minimum number of instances (default: 0)
optional uint32 max_instances = 6; // Maximum number of instances (null means unlimited)
}

message Session {
Expand Down
8 changes: 6 additions & 2 deletions sdk/python/src/flamepy/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,19 @@ def connect(addr: str) -> "Connection":
return Connection.connect(addr)


def create_session(application: str, common_data: Optional[bytes] = None, session_id: Optional[str] = None, slots: int = 1) -> "Session":
def create_session(application: str, common_data: Optional[bytes] = None, session_id: Optional[str] = None, slots: int = 1, min_instances: int = 0, max_instances: Optional[int] = None) -> "Session":
"""Create a new session.

Args:
application: Application name
common_data: Common data as bytes (core API works with bytes)
session_id: Optional session ID
slots: Number of slots
min_instances: Minimum number of instances (default: 0)
max_instances: Maximum number of instances (None = unlimited)
"""
conn = ConnectionInstance.instance()
return conn.create_session(SessionAttributes(id=session_id, application=application, common_data=common_data, slots=slots))
return conn.create_session(SessionAttributes(id=session_id, application=application, common_data=common_data, slots=slots, min_instances=min_instances, max_instances=max_instances))


def open_session(session_id: SessionID) -> "Session":
Expand Down Expand Up @@ -333,6 +335,8 @@ def create_session(self, attrs: SessionAttributes) -> "Session":
application=attrs.application,
slots=attrs.slots,
common_data=common_data_bytes,
min_instances=attrs.min_instances,
max_instances=attrs.max_instances if attrs.max_instances is not None else None,
)

request = CreateSessionRequest(session_id=session_id, session=session_spec)
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/src/flamepy/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class SessionAttributes:
slots: int
id: Optional[str] = None
common_data: Any = None
min_instances: int = 0 # Minimum number of instances (default: 0)
max_instances: Optional[int] = None # Maximum number of instances (None = unlimited)


@dataclass
Expand Down
Loading
Loading