1use std::{env, io::Error};
2use std::collections::HashMap;
3use std::env::VarError;
4use std::net::{AddrParseError, SocketAddr};
5use std::sync::Arc;
6use futures::executor::block_on;
7use futures::TryFutureExt;
8use local_ip_address::local_ip;
9use nacos_sdk::api::constants;
10use nacos_sdk::api::naming::{NamingService, NamingServiceBuilder, ServiceInstance};
11use nacos_sdk::api::props::ClientProps;
12use tracing::info;
13
14const META_GRPC_PORT: &'static str = "gRPC_port";
15
16#[derive(Debug)]
17pub enum EzError {
18 IO(Error),
19 Env(VarError, String),
20 Parse(AddrParseError),
21 LocalIP(local_ip_address::Error),
22 Other(String)
23}
24
25impl std::fmt::Display for EzError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 EzError::IO(err) => write!(f,"IO Error: {}", err),
29 EzError::Env(err, name) => write!(f,"Read environment variables [{}] error: {}", name, err),
30 EzError::Parse(err) => write!(f,"Parse error: {}", err),
31 EzError::LocalIP(err) => write!(f,"Local IP error: {}", err),
32 EzError::Other(msg) => write!(f, "Other error: {}", msg),
33 }
34 }
35}
36
37impl std::error::Error for EzError {
38 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39 match self {
40 EzError::IO(err) => Some(err),
41 EzError::Env(err, _) => Some(err),
42 EzError::Parse(err) => Some(err),
43 EzError::LocalIP(err) => Some(err),
44 _ => None,
45 }
46 }
47}
48
49impl From<Error> for EzError {
50 fn from(value: Error) -> Self {
51 EzError::IO(value)
52 }
53}
54
55impl From<AddrParseError> for EzError {
56 fn from(value: AddrParseError) -> Self {
57 EzError::Parse(value)
58 }
59}
60
61impl From<local_ip_address::Error> for EzError {
62 fn from(value: local_ip_address::Error) -> Self {
63 EzError::LocalIP(value)
64 }
65}
66
67pub struct ServeOptions {
68 pub addr: Option<String>,
69 pub namespace: Option<String>,
70 pub service_addr: Option<String>,
71 pub service_name: Option<String>,
72 pub service_host: Option<String>,
73}
74
75#[derive(Debug, Clone)]
76pub struct ServiceManager {
77 pub naming_service: Arc<NamingService>,
78 pub service_instance: ServiceInstance,
79 pub service_name: String,
80}
81
82impl ServiceManager {
83 pub fn new(opt: ServeOptions) -> Result<Self,EzError> {
84 let addr = parse_addr(match opt.addr {
85 Some(addr) => addr,
86 None => get_env("NACOS_ADDR")?
87 })?;
88 let namespace = match opt.namespace {
89 Some(namespace) => namespace,
90 None => get_env("NACOS_NAMESPACE")?
91 };
92 let service_addr = parse_addr(match opt.service_addr {
93 Some(service_addr) => parse_addr(service_addr)?,
94 None => get_env("SERVICE_ADDR")?
95 })?;
96 let service_name = match opt.service_name {
97 Some(service_name) => service_name,
98 None => get_env("SERVICE_NAME")?
99 };
100 let local_ip = local_ip()?.to_string();
101 let service_host = opt.service_host.unwrap_or_else(|| get_env("SERVICE_HOST").unwrap_or(local_ip));
102 info!("[NACOS_ADDR]: {}", addr);
103 info!("[NACOS_NAMESPACE]: {}", namespace);
104 info!("[SERVICE_ADDR]: {}", service_addr);
105 info!("[SERVICE_NAME]: {}", service_name);
106 info!("[SERVICE_HOST]: {}", service_host);
107 let naming_service = NamingServiceBuilder::new(
108 ClientProps::new().server_addr(addr).namespace(namespace))
109 .build()
110 .map_err(|e| {
111 EzError::Other(format!("NamingService create failed: {}", e))
112 })?;
113 let (_, port) = match service_addr.split_once(":") {
114 Some(value) => value,
115 None => return Err(EzError::Other("Invalid service address".to_string()))
116 };
117
118
119 let instance = ServiceInstance{
120 ip: service_host,
121 port: port.parse::<i32>().unwrap(),
122 weight: 1.0,
123 healthy: true,
124 enabled: true,
125 ephemeral: true,
126 metadata: HashMap::from([(META_GRPC_PORT.to_string(), port.to_string())]),
127 ..Default::default()
128 };
129 Ok(Self{
130 naming_service: Arc::new(naming_service),
131 service_instance: instance,
132 service_name,
133 })
134 }
135
136 pub fn online(&self) -> Result<(),EzError> {
137 block_on(self.naming_service.register_instance(self.service_name.clone(), Some(constants::DEFAULT_GROUP.to_string()), self.service_instance.clone()))
138 .map_err(|e| EzError::Other(format!("Service online error: {}", e.to_string())))?;
139 info!("Service online successfully");
140 Ok(())
141 }
142 pub fn offline(&self) -> Result<(), EzError> {
143 block_on(self.naming_service.deregister_instance(self.service_name.clone(), Some(constants::DEFAULT_GROUP.to_string()), self.service_instance.clone())
144 .map_err(|e| EzError::Other(format!("Service offline error: {}", e.to_string()))))?;
145 info!("Service offline successfully");
146 Ok(())
147 }
148}
149
150impl Default for ServeOptions {
151 fn default() -> Self {
152 Self{
153 addr: None,
154 namespace: None,
155 service_addr: None,
156 service_name: None,
157 service_host: None,
158 }
159 }
160}
161
162fn get_env(name: &str) -> Result<String, EzError> {
163 let res = env::var(name);
164 match res {
165 Ok(val) => Ok(val),
166 Err(err) => Err(EzError::Env(err, String::from(name)))
167 }
168}
169
170fn parse_addr(addr: String) -> Result<String, EzError> {
171 match addr.parse::<SocketAddr>() {
172 Ok(_) => Ok(addr),
173 Err(err) => Err(EzError::Parse(err))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::thread::sleep;
181 #[test]
182 fn test_online(){
183 let manager = ServiceManager::new(ServeOptions::default()).unwrap();
184 manager.online().unwrap();
185 sleep(std::time::Duration::from_secs(10));
186 manager.offline().unwrap();
187 }
188}