1use std::{fmt::Display, net::IpAddr};
4
5use ipnet::IpNet;
6use serde::{Deserialize, Serialize};
7use time::Date;
8
9use crate::{error::Error, urlencode::UrlEncode, AsyncRobot};
10
11use super::{server::ServerId, wrapper::Empty, UnauthenticatedRequest};
12
13fn list_vswitches() -> UnauthenticatedRequest<Vec<VSwitchReference>> {
14 UnauthenticatedRequest::from("https://robot-ws.your-server.de/vswitch")
15}
16
17fn get_vswitch(vswitch: VSwitchId) -> UnauthenticatedRequest<InternalVSwitch> {
18 UnauthenticatedRequest::from(&format!(
19 "https://robot-ws.your-server.de/vswitch/{vswitch}"
20 ))
21}
22
23#[derive(Serialize)]
24struct UpdateVSwitch<'a> {
25 name: &'a str,
26 vlan: u16,
27}
28
29fn create_vswitch(
30 name: &str,
31 vlan_id: VlanId,
32) -> Result<UnauthenticatedRequest<VSwitchReference>, serde_html_form::ser::Error> {
33 UnauthenticatedRequest::from("https://robot-ws.your-server.de/vswitch")
34 .with_method("POST")
35 .with_body(UpdateVSwitch {
36 name,
37 vlan: vlan_id.0,
38 })
39}
40
41fn update_vswitch(
42 vswitch_id: VSwitchId,
43 name: &str,
44 vlan_id: VlanId,
45) -> Result<UnauthenticatedRequest<Empty>, serde_html_form::ser::Error> {
46 UnauthenticatedRequest::from(&format!(
47 "https://robot-ws.your-server.de/vswitch/{vswitch_id}"
48 ))
49 .with_method("POST")
50 .with_body(UpdateVSwitch {
51 name,
52 vlan: vlan_id.0,
53 })
54}
55
56fn delete_vswitch(vswitch_id: VSwitchId, date: Option<Date>) -> UnauthenticatedRequest<Empty> {
57 let date = date
58 .map(|date| date.to_string())
59 .unwrap_or("now".to_string());
60
61 UnauthenticatedRequest::from(&format!(
62 "https://robot-ws.your-server.de/vswitch/{vswitch_id}"
63 ))
64 .with_method("DELETE")
65 .with_serialized_body(format!("cancellation_date={date}"))
66}
67
68#[derive(Debug, Clone, Serialize)]
69struct ServerList<'a> {
70 server: &'a [ServerId],
71}
72
73impl<'a> UrlEncode for ServerList<'a> {
74 fn encode_into(&self, mut f: crate::urlencode::UrlEncodingBuffer<'_>) {
75 for server in self.server {
76 f.set("server[]", server);
77 }
78 }
79}
80
81fn add_servers(vswitch_id: VSwitchId, servers: &[ServerId]) -> UnauthenticatedRequest<Empty> {
82 UnauthenticatedRequest::from(&format!(
83 "https://robot-ws.your-server.de/vswitch/{vswitch_id}/server"
84 ))
85 .with_method("POST")
86 .with_serialized_body(ServerList { server: servers }.encode())
87}
88
89fn remove_servers(vswitch_id: VSwitchId, servers: &[ServerId]) -> UnauthenticatedRequest<Empty> {
90 UnauthenticatedRequest::from(&format!(
91 "https://robot-ws.your-server.de/vswitch/{vswitch_id}/server"
92 ))
93 .with_method("DELETE")
94 .with_serialized_body(ServerList { server: servers }.encode())
95}
96
97impl AsyncRobot {
98 pub async fn list_vswitches(&self) -> Result<Vec<VSwitchReference>, Error> {
109 self.go(list_vswitches()).await
110 }
111
112 pub async fn get_vswitch(&self, vswitch: VSwitchId) -> Result<VSwitch, Error> {
124 Ok(self.go(get_vswitch(vswitch)).await?.into())
125 }
126
127 pub async fn create_vswitch(
139 &self,
140 name: &str,
141 vlan_id: VlanId,
142 ) -> Result<VSwitchReference, Error> {
143 self.go(create_vswitch(name, vlan_id)?).await
144 }
145
146 pub async fn update_vswitch(
162 &self,
163 vswitch_id: VSwitchId,
164 name: &str,
165 vlan_id: VlanId,
166 ) -> Result<(), Error> {
167 self.go(update_vswitch(vswitch_id, name, vlan_id)?)
168 .await?
169 .throw_away();
170 Ok(())
171 }
172
173 pub async fn cancel_vswitch(
191 &self,
192 vswitch_id: VSwitchId,
193 cancellation_date: Option<Date>,
194 ) -> Result<(), Error> {
195 self.go(delete_vswitch(vswitch_id, cancellation_date))
196 .await?
197 .throw_away();
198 Ok(())
199 }
200
201 pub async fn connect_vswitch_servers(
217 &self,
218 vswitch_id: VSwitchId,
219 server_ids: &[ServerId],
220 ) -> Result<(), Error> {
221 self.go(add_servers(vswitch_id, server_ids))
222 .await?
223 .throw_away();
224 Ok(())
225 }
226
227 pub async fn disconnect_vswitch_servers(
243 &self,
244 vswitch_id: VSwitchId,
245 server_ids: &[ServerId],
246 ) -> Result<(), Error> {
247 self.go(remove_servers(vswitch_id, server_ids))
248 .await?
249 .throw_away();
250 Ok(())
251 }
252}
253
254#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
262pub struct VlanId(pub u16);
263
264impl From<u16> for VlanId {
265 fn from(value: u16) -> Self {
266 VlanId(value)
267 }
268}
269
270impl From<VlanId> for u16 {
271 fn from(value: VlanId) -> Self {
272 value.0
273 }
274}
275
276impl Display for VlanId {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 self.0.fmt(f)
279 }
280}
281
282impl PartialEq<u16> for VlanId {
283 fn eq(&self, other: &u16) -> bool {
284 self.0.eq(other)
285 }
286}
287
288#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub struct VSwitchId(pub u32);
299
300impl From<u32> for VSwitchId {
301 fn from(value: u32) -> Self {
302 VSwitchId(value)
303 }
304}
305
306impl From<VSwitchId> for u32 {
307 fn from(value: VSwitchId) -> Self {
308 value.0
309 }
310}
311
312impl Display for VSwitchId {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 self.0.fmt(f)
315 }
316}
317
318impl PartialEq<u32> for VSwitchId {
319 fn eq(&self, other: &u32) -> bool {
320 self.0.eq(other)
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct VSwitchReference {
331 pub id: VSwitchId,
333
334 pub name: String,
336
337 pub vlan: VlanId,
341
342 pub cancelled: bool,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347struct InternalVSwitch {
348 pub id: VSwitchId,
349 pub name: String,
350 pub vlan: VlanId,
351 pub cancelled: bool,
352 pub server: Vec<VSwitchServer>,
353 pub subnet: Vec<InternalSubnet>,
354 pub cloud_network: Vec<InternalCloudNetwork>,
355}
356
357impl From<InternalVSwitch> for VSwitch {
358 fn from(value: InternalVSwitch) -> Self {
359 VSwitch {
360 id: value.id,
361 name: value.name,
362 vlan: value.vlan,
363 cancelled: value.cancelled,
364 servers: value.server,
365 subnets: value.subnet.into_iter().map(IpNet::from).collect(),
366 cloud_networks: value
367 .cloud_network
368 .into_iter()
369 .map(CloudNetwork::from)
370 .collect(),
371 }
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
376struct InternalSubnet {
377 pub ip: IpAddr,
378 pub mask: u8,
379}
380
381impl From<InternalSubnet> for IpNet {
382 fn from(value: InternalSubnet) -> Self {
383 IpNet::new(value.ip, value.mask).unwrap()
384 }
385}
386
387#[derive(Debug, Clone)]
389pub struct VSwitch {
390 pub id: VSwitchId,
392
393 pub name: String,
395
396 pub vlan: VlanId,
398
399 pub cancelled: bool,
401
402 pub servers: Vec<VSwitchServer>,
404
405 pub subnets: Vec<IpNet>,
407
408 pub cloud_networks: Vec<CloudNetwork>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
418pub enum ConnectionStatus {
419 #[serde(rename = "ready")]
421 Ready,
422
423 #[serde(rename = "in process", alias = "processing")]
425 InProcess,
426
427 #[serde(rename = "failed")]
429 Failed,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct VSwitchServer {
435 #[serde(rename = "server_number")]
437 pub id: ServerId,
438
439 pub status: ConnectionStatus,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444struct InternalCloudNetwork {
445 pub id: CloudNetworkId,
446 pub ip: IpAddr,
447 pub mask: u8,
448}
449
450impl From<InternalCloudNetwork> for CloudNetwork {
451 fn from(value: InternalCloudNetwork) -> Self {
452 CloudNetwork {
453 id: value.id,
454 network: IpNet::new(value.ip, value.mask).unwrap(),
455 }
456 }
457}
458
459#[derive(Debug, Clone, PartialEq, Eq)]
461pub struct CloudNetwork {
462 pub id: CloudNetworkId,
464
465 pub network: IpNet,
467}
468
469#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
473pub struct CloudNetworkId(pub u32);
474
475impl From<u32> for CloudNetworkId {
476 fn from(value: u32) -> Self {
477 CloudNetworkId(value)
478 }
479}
480
481impl From<CloudNetworkId> for u32 {
482 fn from(value: CloudNetworkId) -> Self {
483 value.0
484 }
485}
486
487impl Display for CloudNetworkId {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 self.0.fmt(f)
490 }
491}
492
493impl PartialEq<u32> for CloudNetworkId {
494 fn eq(&self, other: &u32) -> bool {
495 self.0.eq(other)
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use std::{
502 net::{IpAddr, Ipv4Addr},
503 str::FromStr,
504 };
505
506 use ipnet::{IpNet, Ipv4Net};
507
508 use crate::api::vswitch::{
509 CloudNetwork, CloudNetworkId, InternalCloudNetwork, InternalSubnet, VSwitchId, VlanId,
510 };
511
512 use super::InternalVSwitch;
513
514 #[test]
515 fn deserialize_vswitch() {
516 let json = r#"
517 {
518 "id": 50301,
519 "name": "hrobot-test-vswitch-AOLwCPri-re",
520 "vlan":4001,
521 "cancelled":false,
522 "server":[
523 {
524 "server_number": 2321379,
525 "server_ip": "138.201.21.47",
526 "server_ipv6_net": "2a01:4f8:171:2c2c::",
527 "status": "processing"
528 }
529 ],
530 "subnet": [],
531 "cloud_network": []
532 }"#;
533
534 let _ = serde_json::from_str::<InternalVSwitch>(json).unwrap();
535 }
536
537 #[test]
538 fn vlan_construction() {
539 assert_eq!(VlanId::from(4001u16), 4001);
540
541 assert_eq!(VlanId(4001).to_string(), "4001");
542 }
543
544 #[test]
545 fn vswitch_id_construction() {
546 assert_eq!(VSwitchId::from(10101u32), 10101u32);
547 assert_eq!(
548 VSwitchId::from(10101u32),
549 u32::from(VSwitchId::from(10101u32)),
550 );
551 }
552
553 #[test]
554 fn internal_subnet_conversion() {
555 assert_eq!(
556 IpNet::from(InternalSubnet {
557 ip: IpAddr::from_str("127.0.0.0").unwrap(),
558 mask: 24
559 }),
560 IpNet::V4(Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 0), 24).unwrap())
561 );
562 }
563
564 #[test]
565 fn cloud_network_construction() {
566 assert_eq!(
567 CloudNetwork::from(InternalCloudNetwork {
568 id: CloudNetworkId::from(10),
569 ip: Ipv4Addr::LOCALHOST.into(),
570 mask: 8
571 }),
572 CloudNetwork {
573 id: CloudNetworkId(10),
574 network: IpNet::new(Ipv4Addr::LOCALHOST.into(), 8).unwrap()
575 }
576 );
577
578 assert_eq!(u32::from(CloudNetworkId(10)), 10);
579 assert_eq!(CloudNetworkId(10), 10);
580 }
581}