Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F73772
instance.rs
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Subscribers
None
instance.rs
View Options
use
std
::
sync
::
Arc
;
use
axum
::
{
extract
::
{
Path
,
State
},
http
::
StatusCode
,
response
::
IntoResponse
,
Json
,
};
use
serde
::
{
Deserialize
,
Serialize
};
use
store
::
instance
::
{
Instance
,
InstanceConfig
,
NetworkInterface
};
use
std
::
collections
::
HashMap
;
use
crate
::
AppState
;
#[derive(Serialize, Deserialize)]
pub
struct
CreateInstanceRequest
{
pub
name
:
String
,
pub
provider_type
:
String
,
pub
provider_config
:
serde_json
::
Value
,
pub
network_interfaces
:
Option
<
Vec
<
NetworkInterfaceRequest
>>
,
pub
metadata
:
Option
<
HashMap
<
String
,
String
>>
,
}
#[derive(Serialize, Deserialize)]
pub
struct
NetworkInterfaceRequest
{
pub
network_name
:
String
,
pub
interface_name
:
String
,
}
#[derive(Serialize, Deserialize)]
pub
struct
UpdateInstanceRequest
{
pub
provider_config
:
Option
<
serde_json
::
Value
>
,
pub
network_interfaces
:
Option
<
Vec
<
NetworkInterfaceRequest
>>
,
pub
metadata
:
Option
<
HashMap
<
String
,
String
>>
,
}
pub
async
fn
get_instances
(
State
(
state
)
:
State
<
Arc
<
AppState
>>
)
->
impl
IntoResponse
{
let
instances
=
state
.
collar
.
store
.
instances
();
match
instances
.
list
()
{
Ok
(
instance_list
)
=>
(
StatusCode
::
OK
,
Json
(
instance_list
)).
into_response
(),
Err
(
e
)
=>
(
StatusCode
::
INTERNAL_SERVER_ERROR
,
format!
(
"Failed to list instances: {:?}"
,
e
)).
into_response
(),
}
}
pub
async
fn
post_instances
(
State
(
state
)
:
State
<
Arc
<
AppState
>>
,
Json
(
req
)
:
Json
<
CreateInstanceRequest
>
)
->
impl
IntoResponse
{
let
instances
=
state
.
collar
.
store
.
instances
();
// Validate provider type
if
!
matches!
(
req
.
provider_type
.
as_str
(),
"jail"
|
"container"
)
{
return
(
StatusCode
::
BAD_REQUEST
,
format!
(
"Invalid provider type '{}'. Must be 'jail' or 'container'"
,
req
.
provider_type
)).
into_response
();
}
// Convert network interfaces
let
network_interfaces
=
req
.
network_interfaces
.
unwrap_or_default
()
.
into_iter
()
.
map
(
|
ni
|
NetworkInterface
{
network_name
:
ni
.
network_name
,
interface_name
:
ni
.
interface_name
,
assignment
:
None
,
// Will be assigned during creation
})
.
collect
();
// Create instance config
let
config
=
InstanceConfig
{
provider_config
:
req
.
provider_config
.
to_string
(),
network_interfaces
,
metadata
:
req
.
metadata
.
unwrap_or_default
(),
};
// Create instance
let
instance
=
Instance
::
new
(
req
.
name
.
clone
(),
req
.
provider_type
,
config
);
match
instances
.
create
(
instance
)
{
Ok
(
created_instance
)
=>
(
StatusCode
::
CREATED
,
Json
(
created_instance
)).
into_response
(),
Err
(
e
)
=>
(
StatusCode
::
INTERNAL_SERVER_ERROR
,
format!
(
"Failed to create instance '{}': {:?}"
,
req
.
name
,
e
)).
into_response
(),
}
}
pub
async
fn
get_instance
(
State
(
state
)
:
State
<
Arc
<
AppState
>>
,
Path
(
name
)
:
Path
<
String
>
)
->
impl
IntoResponse
{
let
instances
=
state
.
collar
.
store
.
instances
();
match
instances
.
get
(
&
name
)
{
Ok
(
instance
)
=>
(
StatusCode
::
OK
,
Json
(
instance
)).
into_response
(),
Err
(
e
)
=>
{
if
e
.
to_string
().
contains
(
"not found"
)
{
(
StatusCode
::
NOT_FOUND
,
format!
(
"Instance '{}' not found"
,
name
)).
into_response
()
}
else
{
(
StatusCode
::
INTERNAL_SERVER_ERROR
,
format!
(
"Failed to get instance '{}': {:?}"
,
name
,
e
)).
into_response
()
}
}
}
}
pub
async
fn
delete_instance
(
State
(
state
)
:
State
<
Arc
<
AppState
>>
,
Path
(
name
)
:
Path
<
String
>
)
->
impl
IntoResponse
{
let
instances
=
state
.
collar
.
store
.
instances
();
// Check if instance exists first
match
instances
.
exists
(
&
name
)
{
Ok
(
false
)
=>
return
(
StatusCode
::
NOT_FOUND
,
format!
(
"Instance '{}' not found"
,
name
)).
into_response
(),
Err
(
e
)
=>
return
(
StatusCode
::
INTERNAL_SERVER_ERROR
,
format!
(
"Failed to check instance '{}': {:?}"
,
name
,
e
)).
into_response
(),
Ok
(
true
)
=>
{}
}
match
instances
.
delete
(
&
name
)
{
Ok
(())
=>
(
StatusCode
::
OK
,
format!
(
"Deleted instance '{}'"
,
name
)).
into_response
(),
Err
(
e
)
=>
(
StatusCode
::
INTERNAL_SERVER_ERROR
,
format!
(
"Failed to delete instance '{}': {:?}"
,
name
,
e
)).
into_response
(),
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
use
axum
::
http
::
StatusCode
;
use
axum_test
::
TestServer
;
use
serde_json
::
json
;
async
fn
setup_test_server
()
->
TestServer
{
let
collar
=
libcollar
::
new_test
(
&
format!
(
"test_api_instance_{}"
,
std
::
process
::
id
())).
unwrap
();
let
app
=
crate
::
app
(
collar
);
TestServer
::
new
(
app
).
unwrap
()
}
#[tokio::test]
async
fn
test_create_and_get_instance
()
{
let
server
=
setup_test_server
().
await
;
let
create_req
=
json
!
({
"name"
:
"test-instance"
,
"provider_type"
:
"jail"
,
"provider_config"
:
{
"jail_conf"
:
"/etc/jail.conf"
,
"dataset"
:
"zroot/jails"
}
});
let
response
=
server
.
post
(
"/instances"
).
json
(
&
create_req
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
CREATED
);
let
response
=
server
.
get
(
"/instances/test-instance"
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
OK
);
let
instance
:
Instance
=
response
.
json
();
assert_eq!
(
instance
.
name
,
"test-instance"
);
assert_eq!
(
instance
.
provider_type
,
"jail"
);
}
#[tokio::test]
async
fn
test_list_instances
()
{
let
server
=
setup_test_server
().
await
;
let
create_req
=
json
!
({
"name"
:
"list-test-instance"
,
"provider_type"
:
"container"
,
"provider_config"
:
{
"runtime"
:
"docker"
,
"image"
:
"alpine:latest"
}
});
server
.
post
(
"/instances"
).
json
(
&
create_req
).
await
;
let
response
=
server
.
get
(
"/instances"
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
OK
);
let
instances
:
Vec
<
Instance
>
=
response
.
json
();
assert!
(
!
instances
.
is_empty
());
assert!
(
instances
.
iter
().
any
(
|
i
|
i
.
name
==
"list-test-instance"
));
}
#[tokio::test]
async
fn
test_delete_instance
()
{
let
server
=
setup_test_server
().
await
;
let
create_req
=
json
!
({
"name"
:
"delete-test-instance"
,
"provider_type"
:
"jail"
,
"provider_config"
:
{}
});
server
.
post
(
"/instances"
).
json
(
&
create_req
).
await
;
let
response
=
server
.
delete
(
"/instances/delete-test-instance"
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
OK
);
let
response
=
server
.
get
(
"/instances/delete-test-instance"
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
NOT_FOUND
);
}
#[tokio::test]
async
fn
test_invalid_provider_type
()
{
let
server
=
setup_test_server
().
await
;
let
create_req
=
json
!
({
"name"
:
"invalid-provider-instance"
,
"provider_type"
:
"invalid"
,
"provider_config"
:
{}
});
let
response
=
server
.
post
(
"/instances"
).
json
(
&
create_req
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
BAD_REQUEST
);
}
#[tokio::test]
async
fn
test_instance_with_network_interfaces
()
{
let
server
=
setup_test_server
().
await
;
// First create a network
let
network_req
=
json
!
({
"name"
:
"test-network"
,
"cidr"
:
"192.168.1.0/24"
});
server
.
post
(
"/networks"
).
json
(
&
network_req
).
await
;
let
create_req
=
json
!
({
"name"
:
"networked-instance"
,
"provider_type"
:
"jail"
,
"provider_config"
:
{},
"network_interfaces"
:
[
{
"network_name"
:
"test-network"
,
"interface_name"
:
"eth0"
}
]
});
let
response
=
server
.
post
(
"/instances"
).
json
(
&
create_req
).
await
;
assert_eq!
(
response
.
status_code
(),
StatusCode
::
CREATED
);
let
instance
:
Instance
=
response
.
json
();
assert_eq!
(
instance
.
config
.
network_interfaces
.
len
(),
1
);
assert_eq!
(
instance
.
config
.
network_interfaces
[
0
].
network_name
,
"test-network"
);
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jun 9, 11:52 AM (17 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47645
Default Alt Text
instance.rs (8 KB)
Attached To
rCOLLAR collar
Event Timeline
Log In to Comment