├── conf └── nacos-coredns.cfg ├── .gitignore ├── nacos ├── domain_cache.go ├── dns_cache.go ├── host.go ├── util_and_comms_test.go ├── dns_cache_test.go ├── domain_updater.go ├── server_manager_test.go ├── httpclient_test.go ├── nacos_domain_test.go ├── nacos_domain.go ├── udp_server_test.go ├── setup_test.go ├── server_manager.go ├── util_and_comms.go ├── utilconfig.go ├── httpclient.go ├── setup.go ├── udp_server.go ├── nacos_client_test.go ├── nacos.go ├── nacos_grpc_client_test.go ├── nacos_grpc_client.go ├── concurrent_map.go └── nacos_client.go ├── bin ├── build.sh └── build-mac.sh ├── README_CN.md ├── README.md ├── forward └── setup.go └── LICENSE /conf/nacos-coredns.cfg: -------------------------------------------------------------------------------- 1 | . { 2 | log 3 | nacos { 4 | nacos_namespaceId public 5 | nacos_server_host console.nacos.io:8848 6 | } 7 | forward . /etc/resolv.conf 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /nacos/domain_cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | type DomCache map[string]Domain 17 | type Servers []string 18 | -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # cd GOPATH 3 | cd $GOPATH/src/ 4 | 5 | # remove codes 6 | rm -rf coredns 7 | rm -rf nacos-coredns-plugin 8 | 9 | # clone current codes 10 | git clone https://github.com/nacos-group/nacos-coredns-plugin.git 11 | git clone https://github.com/coredns/coredns.git 12 | 13 | 14 | # cd coredns directory 15 | cd $GOPATH/src/coredns 16 | git checkout -b v1.9.3 v1.9.3 17 | 18 | # copy nacos plugin to coredns 19 | cp -r ../nacos-coredns-plugin/nacos plugin/ 20 | cp -r ../nacos-coredns-plugin/forward/setup.go plugin/forward 21 | cp -r ../nacos-coredns-plugin/conf conf 22 | 23 | # insert nacos into plugin 24 | sed -i '/hosts/a\\t"nacos",' core/dnsserver/zdirectives.go 25 | sed -i '/coredns\/plugin\/hosts/a\\t_ "github.com/coredns/coredns/plugin/nacos"' core/plugin/zplugin.go 26 | sed -i '/hosts:hosts/a\nacos:nacos' plugin.cfg 27 | 28 | go mod tidy 29 | 30 | # build 31 | make -------------------------------------------------------------------------------- /bin/build-mac.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # cd GOPATH 3 | cd $GOPATH/src/ 4 | 5 | # remove codes 6 | rm -rf coredns 7 | rm -rf nacos-coredns-plugin 8 | 9 | # clone current codes 10 | git clone https://github.com/coredns/coredns.git 11 | git clone https://github.com/nacos-group/nacos-coredns-plugin.git 12 | 13 | # cd coredns directory 14 | cd $GOPATH/src/coredns 15 | git checkout -b v1.6.7 v1.6.7 16 | go get github.com/cihub/seelog 17 | 18 | # copy nacos plugin to coredns 19 | cp -r ../nacos-coredns-plugin/nacos plugin/ 20 | cp -r ../nacos-coredns-plugin/forward/setup.go plugin/forward 21 | 22 | # insert nacos into plugin 23 | sed -i '' '/hosts/a\ 24 | "nacos",\ 25 | ' core/dnsserver/zdirectives.go 26 | sed -i '' '/coredns\/plugin\/hosts/a\ 27 | _ "github.com/coredns/coredns/plugin/nacos"\ 28 | ' core/plugin/zplugin.go 29 | sed -i '' '/hosts:hosts/a\ 30 | nacos:nacos\ 31 | ' plugin.cfg 32 | # modify import 33 | # build 34 | make 35 | -------------------------------------------------------------------------------- /nacos/dns_cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "github.com/miekg/dns" 18 | "time" 19 | ) 20 | 21 | type DnsCache struct { 22 | Msg *dns.Msg 23 | LastUpdateMills int64 24 | } 25 | 26 | func (dnsCache *DnsCache) Updated() bool { 27 | updated := (int)(time.Now().UnixNano()/1000000-dnsCache.LastUpdateMills) < (int)(DNSTTL*1000) 28 | return updated 29 | } 30 | -------------------------------------------------------------------------------- /nacos/host.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import "encoding/json" 17 | 18 | type Instance struct { 19 | IP string 20 | Port int 21 | Weight float64 22 | Valid bool 23 | Unit string 24 | AppUseType string 25 | Site string 26 | } 27 | 28 | func (h Instance) String() string { 29 | bs, err := json.Marshal(&h) 30 | 31 | if err != nil { 32 | return "" 33 | } 34 | 35 | return string(bs) 36 | } 37 | -------------------------------------------------------------------------------- /nacos/util_and_comms_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "bytes" 18 | "compress/gzip" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestTryDecompressData(t *testing.T) { 24 | s := "hello" 25 | var buffer bytes.Buffer 26 | writer := gzip.NewWriter(&buffer) 27 | writer.Write([]byte(s)) 28 | 29 | writer.Close() 30 | 31 | if strings.Compare(TryDecompressData(buffer.Bytes()), s) == 0 { 32 | t.Log("Gzip test is passed.") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nacos/dns_cache_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "github.com/miekg/dns" 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestDnsCache_Updated(t *testing.T) { 23 | dnsCache := DnsCache{&dns.Msg{}, int64(100000)} 24 | 25 | if !dnsCache.Updated() { 26 | t.Log("Out of date test is passed") 27 | } 28 | 29 | dnsCache = DnsCache{&dns.Msg{}, int64(time.Now().UnixNano())} 30 | 31 | if dnsCache.Updated() { 32 | t.Log("Updated is passed.") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nacos/domain_updater.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "sync" 18 | ) 19 | 20 | var domCache = DomCache{} 21 | var AllDoms AllDomsMap 22 | var indexMap = NewConcurrentMap() 23 | var serverManger = ServerManager{} 24 | 25 | type AllDomsMap struct { 26 | Data map[string]bool 27 | CacheSeconds int 28 | DLock sync.RWMutex 29 | } 30 | 31 | type AllDomNames struct { 32 | Doms []string `json:"doms"` 33 | CacheMillis int `json:"cacheMillis"` 34 | } 35 | 36 | type NewAllDomNames struct { 37 | Doms map[string][]string `json:"doms"` 38 | CacheMillis int `json:"cacheMillis"` 39 | } 40 | -------------------------------------------------------------------------------- /nacos/server_manager_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "os" 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestServerManager_NextServer(t *testing.T) { 23 | os.Setenv("nacos_server_list", "2.2.2.2,3.3.3.3") 24 | sm := ServerManager{} 25 | sm.RefreshServerListIfNeed() 26 | ip := sm.NextServer() 27 | if strings.Compare(ip, "2.2.2.2") == 0 || 28 | strings.Compare(ip, "3.3.3.3") == 0 { 29 | t.Log("ServerManager.NextServer test is passed.") 30 | } 31 | } 32 | 33 | func TestServerManager_RefreshServerListIfNeed(t *testing.T) { 34 | os.Setenv("nacos_server_list", "2.2.2.2,3.3.3.3") 35 | sm := ServerManager{} 36 | sm.RefreshServerListIfNeed() 37 | 38 | if len(sm.serverList) == 2 { 39 | t.Log("ServerManager.RefreshServerListIfNeed test is passed.") 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /nacos/httpclient_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "net/http" 18 | "net/http/httptest" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestGet(t *testing.T) { 24 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 25 | 26 | if req.URL.EscapedPath() != "/nacos/v1/ns/api/srvIPXT" { 27 | t.Errorf("Except to path '/person',got '%s'", req.URL.EscapedPath()) 28 | w.WriteHeader(http.StatusNotFound) 29 | } else { 30 | w.WriteHeader(http.StatusOK) 31 | w.Write([]byte("hello")) 32 | } 33 | 34 | })) 35 | defer server.Close() 36 | 37 | s := Get(server.URL+"/nacos/v1/ns/api/srvIPXT", nil) 38 | 39 | if strings.Compare(s, "hello") == 0 { 40 | t.Log("Success to test http client get") 41 | } else { 42 | t.Fatal("Failed to test http client get") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /nacos/nacos_domain_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "strings" 18 | "testing" 19 | ) 20 | 21 | func TestDomain_SrvInstances(t *testing.T) { 22 | domain := Domain{} 23 | domain.CacheMillis = 10000 24 | domain.Clusters = "DEFAULT" 25 | 26 | //test weight 27 | domain.Instances = []Instance{Instance{IP: "2.2.2.2", Port: 80, Weight: 2, AppUseType: "publish", Valid: true, Site: "et2"}} 28 | instances := domain.SrvInstances() 29 | if len(instances) == 2 { 30 | t.Log("Domain.srvInstances weight passed.") 31 | } 32 | 33 | //test valid 34 | defer func() { 35 | if err := recover(); err != nil { 36 | if strings.HasPrefix(err.(string), "no host to srv: ") { 37 | t.Log("Domain.srvInstances valid passed.") 38 | } 39 | } 40 | }() 41 | domain.Instances = []Instance{Instance{IP: "2.2.2.2", Port: 80, Weight: 2, AppUseType: "publish", Valid: false, Site: "et2"}} 42 | domain.SrvInstances() 43 | 44 | } 45 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # nacos-coredns-plugin [English](./README.md) # 2 | 本项目提供了一个基于CoreDNS的DNS-F客户端,可以将Nacos上注册的服务导出为DNS域名。 本DNS-F客户端是应用程序进程旁边的一个专用代理进程(side car),可以将服务名作为DNS域名查询请求转发到本客户端,提供服务发现的功能。 3 | ## 快速开始 4 | 要构建和运行本 nacos-coredns 插件,操作系统必须是 Linux 或 Mac。 另外,请确保您的 nacos 服务端版本为2.0或更高级版本,以及 golang 版本为 1.17 或更高级版本, 并且必须正确配置 golang 环境(GOPATH、GOROOT)。因为需要支持 nacos2.x 版本的gRPC连接功能和 go mod 功能。 5 | ### 构建 6 | ``` 7 | git clone https://github.com/nacos-group/nacos-coredns-plugin.git 8 | cp nacos-coredns-plugin/bin/build.sh ~/ 9 | cd ~/ 10 | sh build.sh 11 | ``` 12 | ### 配置 13 | 运行本 nacos-coredns 插件,您需要一个配置文件。 一个标准的配置文件如下: 14 | ``` 15 | . { 16 | log 17 | nacos { 18 | nacos_namespaceId public 19 | nacos_server_host console.nacos.io:8848 20 | } 21 | forward . /etc/resolv.conf 22 | } 23 | ``` 24 | * forward:未在 nacos 注册的域名将被转发到upstream。 25 | * nacos_namespaceId:nacos namespaceId,默认为public。 26 | * nacos_server_host:nacos 服务端的IP地址和端口,如果有两个或多个 nacos 服务端,用逗号分隔 27 | 28 | ### 运行 29 | * 首先需要部署一个nacos服务端。 [部署参考](https://github.com/alibaba/nacos) 30 | * 其次,在nacos上注册服务。 31 | * 然后输入配置文件 $path_to_corefile 和指定端口 $dns_port ,运行本项目 32 | 33 | ```$GOPATH/src/coredns/coredns -conf $path_to_corefile -dns.port $dns_port``` 34 | 35 | ![image](https://cdn.nlark.com/yuque/0/2022/png/29425667/1663504581023-95437fee-0e3d-4b6a-851c-44a352dedd81.png) 36 | 37 | ### 服务发现例子 38 | 输入服务名 $nacos_service_name ,本项目部署的IP地址 $dns_ip 和端口 $dns_port 39 | 40 | ```dig $nacos_service_name @$dns_ip -p $dns_port ``` 41 | ![image](https://cdn.nlark.com/yuque/0/2022/png/29425667/1663504588231-341b38fe-da55-41eb-a24b-e3752b86faa4.png) 42 | -------------------------------------------------------------------------------- /nacos/nacos_domain.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "encoding/json" 18 | "math" 19 | ) 20 | 21 | type Domain struct { 22 | Name string `json:"dom"` 23 | Clusters string 24 | CacheMillis int64 25 | LastRefMillis int64 26 | Instances []Instance `json:"hosts"` 27 | Env string 28 | TTL int 29 | } 30 | 31 | func (domain Domain) getInstances() []Instance { 32 | return domain.Instances 33 | } 34 | 35 | func (domain Domain) String() string { 36 | b, _ := json.Marshal(domain) 37 | return string(b) 38 | } 39 | 40 | func (domain Domain) SrvInstances() []Instance { 41 | var result = make([]Instance, 0) 42 | hosts := domain.getInstances() 43 | for _, host := range hosts { 44 | if host.Valid && host.Weight > 0 { 45 | for i := 0; i < int(math.Ceil(host.Weight)); i++ { 46 | result = append(result, host) 47 | } 48 | } 49 | } 50 | 51 | if len(result) <= 0 { 52 | panic("no host to srv: " + domain.Name) 53 | } 54 | 55 | return result 56 | } 57 | -------------------------------------------------------------------------------- /nacos/udp_server_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "net" 18 | "strings" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | func TestUDPServer_StartServer(t *testing.T) { 24 | s := `{"dom":"hello123","cacheMillis":10000,"useSpecifiedURL":false,"hosts":[{"valid":true,"marked":false,"metadata":{},"instanceId":"","port":80,"ip":"2.2.2.2","weight":1.0,"enabled":true}],"checksum":"c7befb32f3bb5b169f76efbb0e1f79eb1542236821437","lastRefTime":1542236821437,"env":"","clusters":""}` 25 | us := UDPServer{} 26 | //us.vipClient = &NacosClient{NewConcurrentMap(), UDPServer{}, ServerManager{}, 8848} 27 | us.vipClient = &NacosClient{NewConcurrentMap(), UDPServer{}} 28 | go us.StartServer() 29 | 30 | time.Sleep(100000) 31 | sip := net.ParseIP("127.0.0.1") 32 | srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0} 33 | dstAddr := &net.UDPAddr{IP: sip, Port: us.port} 34 | conn, err := net.DialUDP("udp", srcAddr, dstAddr) 35 | if err != nil { 36 | t.Error("Udp server test failed") 37 | } 38 | defer conn.Close() 39 | conn.Write([]byte(s)) 40 | data := make([]byte, 4024) 41 | n, _, _ := conn.ReadFromUDP(data) 42 | if strings.Contains(string(data[:n]), "push-ack") { 43 | t.Log("Udp server test passed.") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /nacos/setup_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "strings" 18 | 19 | "github.com/coredns/caddy" 20 | os "os" 21 | "testing" 22 | ) 23 | 24 | func TestNacosParse(t *testing.T) { 25 | tests := []struct { 26 | input string 27 | expectedPath string 28 | expectedEndpoint string 29 | expectedErrContent string // substring from the expected error. Empty for positive cases. 30 | }{ 31 | { 32 | `nacos { 33 | nacos_namespaceId public 34 | nacos_server_host console.nacos.io:8848 35 | } 36 | `, "skydns", "localhost:300", "", 37 | }, 38 | } 39 | 40 | os.Unsetenv("nacos_server_list") 41 | 42 | for _, test := range tests { 43 | c := caddy.NewTestController("dns", test.input) 44 | _, err := NacosParse(c) 45 | if err != nil { 46 | t.Error("Failed to get instance.") 47 | } else { 48 | if strings.Compare(GrpcClient.namespaceId, "") != 0 { 49 | t.Fatal("Failed") 50 | } 51 | var passed bool 52 | for _, item := range GrpcClient.serverConfigs { 53 | if strings.Compare(item.IpAddr, "console.nacos.io") == 0 && item.Port == 8848 { 54 | t.Log("Passed") 55 | passed = true 56 | } 57 | } 58 | if !passed { 59 | t.Fatal("Failed") 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nacos-coredns-plugin [中文](./README_CN.md) # 2 | This project provides a **DNS-F** client based on [CoreDNS](https://coredns.io/), which can help export those registed services on Nacos as DNS domain. **DNS-F** client is a dedicated agent process(side car) beside the application's process to foward the service discovery DNS domain query request to Nacos. 3 | 4 | ## Quic Start 5 | To build and run nacos coredns plugin, the OS must be Linux or Mac. And also, make sure your nacos version is 2.0 or higher and golang version is 1.17 or higher. And golang environments(GOPATH,GOROOT,GOHOME) must be configured correctly. Because it needs to support the gRPC connection feature of the nacos2.x version and the go mod function. 6 | 7 | ### Build 8 | ``` 9 | git clone https://github.com/nacos-group/nacos-coredns-plugin.git 10 | cp nacos-coredns-plugin/bin/build.sh ~/ 11 | cd ~/ 12 | sh build.sh 13 | ``` 14 | ### Configuration 15 | To run nacos coredns plugin, you need a configuration file. A possible file may be as bellow: 16 | ``` 17 | . { 18 | log 19 | nacos { 20 | nacos_namespaceId public 21 | nacos_server_host 127.0.0.1:8848 22 | } 23 | forward . /etc/resolv.conf 24 | } 25 | ``` 26 | * forward: domain names those not registered in nacos will be forwarded to upstream. 27 | * nacos_namespaceId: nacos namespaceId, defalut is public. 28 | * nacos_server_host: Ip and Port of nacos server, seperated by comma if there are two or more nacos servers 29 | 30 | ### Run 31 | * Firstly, you need to deploy nacos server. [Here](https://github.com/alibaba/nacos) 32 | * Secondly, register service on nacos. 33 | * Then run ```$GOPATH/src/coredns/coredns -conf $path_to_corefile -dns.port $dns_port``` 34 | ![image](https://cdn.nlark.com/yuque/0/2022/png/29425667/1663504581023-95437fee-0e3d-4b6a-851c-44a352dedd81.png) 35 | 36 | ### Test 37 | dig $nacos_service_name @127.0.0.1 -p $dns_port 38 | 39 | ![image](https://cdn.nlark.com/yuque/0/2022/png/29425667/1663504588231-341b38fe-da55-41eb-a24b-e3752b86faa4.png) 40 | -------------------------------------------------------------------------------- /nacos/server_manager.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "math/rand" 18 | "os" 19 | "reflect" 20 | "strings" 21 | ) 22 | 23 | type ServerManager struct { 24 | serverList []string 25 | lastRefreshTime int64 26 | cursor int 27 | } 28 | 29 | // get nacos ip list from address by env 30 | func (manager *ServerManager) RefreshServerListIfNeed() []string { 31 | if CurrentMillis()-manager.lastRefreshTime < 60*1000 && len(manager.serverList) > 0 { 32 | return manager.serverList 33 | } 34 | 35 | nacosServerList := os.Getenv("nacos_server_list") 36 | 37 | var list []string 38 | list = strings.Split(nacosServerList, ",") 39 | 40 | var servers []string 41 | for _, line := range list { 42 | if line != "" { 43 | servers = append(servers, strings.TrimSpace(line)) 44 | } 45 | } 46 | 47 | if len(servers) > 0 { 48 | if !reflect.DeepEqual(manager.serverList, servers) { 49 | NacosClientLogger.Info("server list is updated, old: ", manager.serverList, ", new: ", servers) 50 | } 51 | manager.serverList = servers 52 | 53 | manager.lastRefreshTime = CurrentMillis() 54 | } 55 | 56 | return manager.serverList 57 | } 58 | 59 | func (manager *ServerManager) NextServer() string { 60 | manager.RefreshServerListIfNeed() 61 | 62 | if len(manager.serverList) == 0 { 63 | panic("no nacos server avialible.") 64 | } 65 | 66 | return manager.serverList[rand.Intn(len(manager.serverList))] 67 | } 68 | 69 | func (manager *ServerManager) SetServers(servers []string) { 70 | manager.serverList = servers 71 | } 72 | 73 | func (manager *ServerManager) GetServerList() []string { 74 | return manager.serverList 75 | } 76 | -------------------------------------------------------------------------------- /nacos/util_and_comms.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "bytes" 18 | "compress/gzip" 19 | "io/ioutil" 20 | "net" 21 | "time" 22 | ) 23 | 24 | var ( 25 | DefaultCacheMillis = int64(5000) 26 | Version = "Nacos-DNS:v1.0.1" 27 | CachePath string 28 | LogPath string 29 | SEPERATOR = "@@" 30 | GZIP_MAGIC = []byte("\x1F\x8B") 31 | EnableReceivePush = true 32 | UDP_Port = -1 33 | SERVER_PORT = "8848" 34 | ) 35 | 36 | func CurrentMillis() int64 { 37 | return time.Now().UnixNano() / 1e6 38 | } 39 | 40 | func TryDecompressData(data []byte) string { 41 | 42 | if !IsGzipFile(data) { 43 | return string(data) 44 | } 45 | 46 | reader, err := gzip.NewReader(bytes.NewReader(data)) 47 | 48 | if err != nil { 49 | NacosClientLogger.Warn("failed to decompress gzip data", err) 50 | return "" 51 | } 52 | 53 | defer reader.Close() 54 | bs, err1 := ioutil.ReadAll(reader) 55 | 56 | if err1 != nil { 57 | NacosClientLogger.Warn("failed to decompress gzip data", err1) 58 | return "" 59 | } 60 | 61 | return string(bs) 62 | } 63 | 64 | func IsGzipFile(data []byte) bool { 65 | if len(data) < 2 { 66 | return false 67 | } 68 | 69 | return bytes.HasPrefix(data, GZIP_MAGIC) 70 | } 71 | 72 | var localIP = "" 73 | 74 | func LocalIP() string { 75 | if localIP == "" { 76 | addrs, err := net.InterfaceAddrs() 77 | if err != nil { 78 | return "" 79 | } 80 | for _, address := range addrs { 81 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 82 | if ipnet.IP.To4() != nil { 83 | localIP = ipnet.IP.String() 84 | break 85 | } 86 | } 87 | } 88 | } 89 | 90 | return localIP 91 | } 92 | -------------------------------------------------------------------------------- /nacos/utilconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "os" 20 | "os/exec" 21 | "os/user" 22 | "path/filepath" 23 | "runtime" 24 | "strings" 25 | ) 26 | 27 | var DNSDomains = make(map[string]string) 28 | var DNSTTL uint32 = 1 29 | 30 | func Exist(path string) bool { 31 | _, err := os.Stat(path) 32 | return err == nil 33 | } 34 | 35 | func GetCurrentDirectory() string { 36 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 37 | if err != nil { 38 | 39 | } 40 | return dir 41 | } 42 | 43 | func Home() string { 44 | user2, err := user.Current() 45 | if nil == err { 46 | return user2.HomeDir 47 | } 48 | 49 | if "windows" == runtime.GOOS { 50 | return homeWindows() 51 | } 52 | 53 | // Unix-like system, so just assume Unix 54 | return homeUnix() 55 | } 56 | 57 | func homeUnix() string { 58 | // First prefer the HOME environmental variable 59 | if home := os.Getenv("HOME"); home != "" { 60 | return home 61 | } 62 | 63 | // If that fails, try the shell 64 | var stdout bytes.Buffer 65 | cmd := exec.Command("sh", "-c", "eval echo ~$USER") 66 | cmd.Stdout = &stdout 67 | if err := cmd.Run(); err != nil { 68 | panic(err) 69 | } 70 | 71 | result := strings.TrimSpace(stdout.String()) 72 | if result == "" { 73 | panic(errors.New("blank output when reading home directory")) 74 | } 75 | 76 | return result 77 | } 78 | 79 | func homeWindows() string { 80 | drive := os.Getenv("HOMEDRIVE") 81 | path := os.Getenv("HOMEPATH") 82 | home := drive + path 83 | if drive == "" || path == "" { 84 | home = os.Getenv("USERPROFILE") 85 | } 86 | if home == "" { 87 | panic(errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")) 88 | } 89 | 90 | return home 91 | } 92 | -------------------------------------------------------------------------------- /nacos/httpclient.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "io/ioutil" 18 | "net/http" 19 | "net/url" 20 | "strconv" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | var httpClient = http.Client{ 26 | Timeout: time.Duration(10000 * time.Millisecond), 27 | } 28 | 29 | func encodeUrl(urlString string, params map[string]string) string { 30 | params["udpPort"] = strconv.Itoa(UDP_Port) 31 | 32 | if !strings.HasSuffix(urlString, "?") { 33 | urlString += "?" 34 | } 35 | 36 | if params == nil || len(params) == 0 { 37 | return urlString 38 | } 39 | 40 | u := url.Values{} 41 | for key, value := range params { 42 | u.Set(key, value) 43 | } 44 | 45 | return urlString + u.Encode() 46 | } 47 | 48 | func Get(url string, params map[string]string) string { 49 | if params == nil { 50 | params = make(map[string]string) 51 | } 52 | 53 | url = encodeUrl(url, params) 54 | 55 | req, err := http.NewRequest("GET", url, nil) 56 | if err != nil { 57 | NacosClientLogger.Error("failed to build request", err) 58 | return "" 59 | } 60 | 61 | req.Header.Add("Client-Version", Version) 62 | response, err := httpClient.Do(req) 63 | 64 | if err != nil || response.StatusCode != 200 { 65 | NacosClientLogger.Error("error while request from "+url, err) 66 | if err != nil { 67 | NacosClientLogger.Error("error while request from "+url, err) 68 | } else { 69 | NacosClientLogger.Warn("error while request from " + url + ", code: " + strconv.Itoa(response.StatusCode)) 70 | } 71 | return "" 72 | } 73 | 74 | b, err := ioutil.ReadAll(response.Body) 75 | response.Body.Close() 76 | 77 | if err != nil { 78 | NacosClientLogger.Error("failed to get response body: "+url, err) 79 | return "" 80 | } 81 | 82 | bs := string(b) 83 | return bs 84 | } 85 | -------------------------------------------------------------------------------- /nacos/setup.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "fmt" 18 | "github.com/coredns/caddy" 19 | "github.com/coredns/coredns/core/dnsserver" 20 | "github.com/coredns/coredns/plugin" 21 | "strconv" 22 | "strings" 23 | ) 24 | 25 | func init() { 26 | caddy.RegisterPlugin("nacos", caddy.Plugin{ 27 | ServerType: "dns", 28 | Action: setup, 29 | }) 30 | fmt.Println("register nacos plugin") 31 | } 32 | 33 | func setup(c *caddy.Controller) error { 34 | fmt.Println("setup nacos plugin") 35 | dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { 36 | vs, _ := NacosParse(c) 37 | vs.Next = next 38 | Inited = true 39 | return vs 40 | }) 41 | return nil 42 | } 43 | 44 | func NacosParse(c *caddy.Controller) (*Nacos, error) { 45 | fmt.Println("init nacos plugin...") 46 | nacosImpl := Nacos{} 47 | var serverHosts = make([]string, 0) 48 | namespaceId := "" 49 | for c.Next() { 50 | nacosImpl.Zones = c.RemainingArgs() 51 | 52 | if c.NextBlock() { 53 | for { 54 | switch v := c.Val(); v { 55 | case "nacos_namespaceId": 56 | namespaceId = c.RemainingArgs()[0] 57 | case "nacos_server_host": 58 | serverHosts = strings.Split(c.RemainingArgs()[0], ",") 59 | 60 | //case "nacos_server": 61 | // servers = strings.Split(c.RemainingArgs()[0], ",") 62 | /* it is nacos_servera noop now */ 63 | //case "nacos_server_port": 64 | // port, err := strconv.Atoi(c.RemainingArgs()[0]) 65 | // if err != nil { 66 | // serverPort = port 67 | // } 68 | 69 | case "cache_ttl": 70 | ttl, err := strconv.Atoi(c.RemainingArgs()[0]) 71 | if err != nil { 72 | DNSTTL = uint32(ttl) 73 | } 74 | case "cache_dir": 75 | CachePath = c.RemainingArgs()[0] 76 | case "log_path": 77 | LogPath = c.RemainingArgs()[0] 78 | default: 79 | if c.Val() != "}" { 80 | return &Nacos{}, c.Errf("unknown property '%s'", c.Val()) 81 | } 82 | } 83 | 84 | if !c.Next() { 85 | break 86 | } 87 | } 88 | 89 | } 90 | 91 | client := NewNacosClient(namespaceId, serverHosts) 92 | nacosImpl.NacosClientImpl = client 93 | nacosImpl.DNSCache = NewConcurrentMap() 94 | 95 | return &nacosImpl, nil 96 | } 97 | return &Nacos{}, nil 98 | } 99 | -------------------------------------------------------------------------------- /nacos/udp_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | json "encoding/json" 18 | "math/rand" 19 | "net" 20 | "os" 21 | "strconv" 22 | "time" 23 | ) 24 | 25 | type UDPServer struct { 26 | port int 27 | host string 28 | vipClient *NacosClient 29 | } 30 | 31 | type PushData struct { 32 | PushType string `json:"type"` 33 | Data string `json:"data"` 34 | LastRefTime int64 `json:"lastRefTime"` 35 | } 36 | 37 | func getUdpPort() int { 38 | return 0 39 | } 40 | 41 | func (us *UDPServer) tryListen() (*net.UDPConn, bool) { 42 | addr, err := net.ResolveUDPAddr("udp", us.host+":"+strconv.Itoa(us.port)) 43 | if err != nil { 44 | NacosClientLogger.Error("Can't resolve address: ", err) 45 | return nil, false 46 | } 47 | 48 | conn, err := net.ListenUDP("udp", addr) 49 | if err != nil { 50 | NacosClientLogger.Error("Error listening:", err) 51 | return nil, false 52 | } 53 | 54 | return conn, true 55 | } 56 | 57 | func (us *UDPServer) SetNacosClient(nc *NacosClient) { 58 | us.vipClient = nc 59 | } 60 | 61 | func (us *UDPServer) StartServer() { 62 | var conn *net.UDPConn 63 | 64 | for i := 0; i < 3; i++ { 65 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 66 | port := r.Intn(1000) + 54951 67 | us.port = port 68 | conn1, ok := us.tryListen() 69 | 70 | if ok { 71 | conn = conn1 72 | NacosClientLogger.Info("udp server start, port: " + strconv.Itoa(port)) 73 | break 74 | } 75 | 76 | if !ok && i == 2 { 77 | NacosClientLogger.Critical("failed to start udp server after trying 3 times.") 78 | os.Exit(1) 79 | } 80 | } 81 | 82 | UDP_Port = us.port 83 | 84 | defer conn.Close() 85 | for { 86 | us.handleClient(conn) 87 | } 88 | } 89 | 90 | func (us *UDPServer) handleClient(conn *net.UDPConn) { 91 | data := make([]byte, 4024) 92 | n, remoteAddr, err := conn.ReadFromUDP(data) 93 | if err != nil { 94 | NacosClientLogger.Error("failed to read UDP msg because of ", err) 95 | return 96 | } 97 | 98 | s := TryDecompressData(data[:n]) 99 | 100 | NacosClientLogger.Info("receive push: "+s+" from: ", remoteAddr) 101 | 102 | var pushData PushData 103 | err1 := json.Unmarshal([]byte(s), &pushData) 104 | if err1 != nil { 105 | NacosClientLogger.Warn("failed to process push data, ", err1) 106 | return 107 | } 108 | 109 | service, err1 := ProcessDomainString(pushData.Data) 110 | NacosClientLogger.Info("receive service: ", service) 111 | 112 | if err1 != nil { 113 | NacosClientLogger.Warn("failed to process push data: "+s, err1) 114 | } 115 | 116 | key := GetCacheKey(service.Name, LocalIP()) 117 | 118 | us.vipClient.serviceMap.Set(key, service) 119 | 120 | ack := make(map[string]string) 121 | ack["type"] = "push-ack" 122 | ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10) 123 | ack["data"] = "" 124 | 125 | bs, _ := json.Marshal(ack) 126 | 127 | conn.WriteToUDP(bs, remoteAddr) 128 | } 129 | -------------------------------------------------------------------------------- /nacos/nacos_client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "github.com/nacos-group/nacos-sdk-go/v2/model" 18 | "github.com/stretchr/testify/assert" 19 | "net/http" 20 | "net/http/httptest" 21 | "sync" 22 | "testing" 23 | ) 24 | 25 | func TestNacosClient_GetDomain(t *testing.T) { 26 | s := `{"dom":"hello123","cacheMillis":10000,"useSpecifiedURL":false,"hosts":[{"valid":true,"marked":false,"metadata":{},"instanceId":"","port":81,"ip":"2.2.2.2","weight":1.0,"enabled":true}],"checksum":"c7befb32f3bb5b169f76efbb0e1f79eb1542236821437","lastRefTime":1542236821437,"env":"","clusters":""}` 27 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 28 | if req.URL.EscapedPath() == "/nacos/v1/ns/api/srvIPXT" { 29 | w.WriteHeader(http.StatusOK) 30 | w.Write([]byte(s)) 31 | } else if req.URL.EscapedPath() == "/nacos/v1/ns/api/allDomNames" { 32 | w.WriteHeader(http.StatusOK) 33 | w.Write([]byte("{\"count\":1,\"doms\":[\"hello123\"]}")) 34 | } 35 | 36 | })) 37 | 38 | //port, _ := strconv.Atoi(strings.Split(server.URL, ":")[2]) 39 | 40 | defer server.Close() 41 | 42 | //vc := NacosClient{NewConcurrentMap(), UDPServer{}, ServerManager{}, port} 43 | //vc.udpServer.vipClient = &vc 44 | //vc.SetServers([]string{strings.Split(strings.Split(server.URL, "http://")[1], ":")[0]}) 45 | //instance := vc.SrvInstance("hello123", "127.0.0.1") 46 | //if strings.Compare(instance.IP, "2.2.2.2") == 0 { 47 | // t.Log("Passed") 48 | //} 49 | } 50 | 51 | var nacosClientTest = NewNacosClientTEST() 52 | 53 | func NewNacosClientTEST() *NacosClient { 54 | vc := NacosClient{NewConcurrentMap(), UDPServer{}} 55 | vc.udpServer.vipClient = &vc 56 | AllDoms = AllDomsMap{} 57 | AllDoms.Data = make(map[string]bool) 58 | AllDoms.DLock = sync.RWMutex{} 59 | return &vc 60 | } 61 | 62 | func TestNacosClient_getAllServiceNames(t *testing.T) { 63 | GrpcClient = grpcClientTest 64 | nacosClientTest.getAllServiceNames() 65 | 66 | AllDoms.DLock.Lock() 67 | defer AllDoms.DLock.Unlock() 68 | doms := GrpcClient.GetAllServicesInfo() 69 | 70 | for _, dom := range doms { 71 | assert.True(t, AllDoms.Data[dom]) 72 | } 73 | if len(doms) == len(AllDoms.Data) { 74 | t.Log("Get all serviceName from servers passed") 75 | } else { 76 | t.Error("Get all serviceName from servers error") 77 | } 78 | } 79 | 80 | func TestNacosClient_getServiceNow(t *testing.T) { 81 | GrpcClient = grpcClientTest 82 | nacosClientTest.getAllServiceNames() 83 | testServiceMap := NewConcurrentMap() 84 | 85 | for serviceName, _ := range AllDoms.Data { 86 | nacosClientTest.getServiceNow(serviceName, &nacosClientTest.serviceMap, "0.0.0.0") 87 | } 88 | 89 | for serviceName, _ := range AllDoms.Data { 90 | testService := GrpcClient.GetService(serviceName) 91 | testServiceMap.Set(serviceName, testService) 92 | s, ok := nacosClientTest.GetDomainCache().Get(serviceName) 93 | assert.True(t, ok) 94 | service := s.(model.Service) 95 | assert.True(t, len(service.Hosts) == len(testService.Hosts)) 96 | } 97 | 98 | if len(nacosClientTest.GetDomainCache()) == len(testServiceMap) { 99 | t.Log("Get all servicesInfo from servers passed") 100 | } else { 101 | t.Error("Get all servicesInfo from servers error") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /nacos/nacos.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "github.com/coredns/coredns/plugin" 20 | "github.com/coredns/coredns/request" 21 | "github.com/miekg/dns" 22 | "github.com/nacos-group/nacos-sdk-go/v2/model" 23 | "net" 24 | ) 25 | 26 | type Nacos struct { 27 | Next plugin.Handler 28 | Zones []string 29 | NacosClientImpl *NacosClient 30 | DNSCache ConcurrentMap 31 | } 32 | 33 | func (vs *Nacos) String() string { 34 | b, err := json.Marshal(vs) 35 | 36 | if err != nil { 37 | return "" 38 | } 39 | 40 | return string(b) 41 | } 42 | 43 | func (vs *Nacos) managed(service, clientIP string) bool { 44 | if _, ok := DNSDomains[service]; ok { 45 | return false 46 | } 47 | 48 | defer AllDoms.DLock.RUnlock() 49 | 50 | AllDoms.DLock.RLock() 51 | _, ok1 := AllDoms.Data[service] 52 | 53 | _, inCache := vs.NacosClientImpl.GetDomainCache().Get(service) 54 | 55 | /* 56 | ok1 means service is alive in server 57 | 根据dns请求订阅服务: 58 | 1.服务首次请求, 缓存中没有数据 59 | 2.插件初始化时在缓存文件中缓存了该服务数据, 但未订阅 60 | */ 61 | if ok1 { 62 | if !inCache { 63 | vs.NacosClientImpl.getServiceNow(service, &vs.NacosClientImpl.serviceMap, clientIP) 64 | } 65 | if !GrpcClient.HasSubcribed(service) { 66 | GrpcClient.Subscribe(service) 67 | } 68 | } 69 | 70 | return ok1 || inCache 71 | } 72 | 73 | func (vs *Nacos) getRecordBySession(dom, clientIP string) model.Instance { 74 | host := *vs.NacosClientImpl.SrvInstance(dom, clientIP) 75 | return host 76 | 77 | } 78 | 79 | func (vs *Nacos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { 80 | state := request.Request{W: w, Req: r} 81 | 82 | name := state.QName() 83 | 84 | m := new(dns.Msg) 85 | 86 | clientIP := state.IP() 87 | if clientIP == "127.0.0.1" { 88 | clientIP = LocalIP() 89 | } 90 | 91 | if !vs.managed(name[:len(name)-1], clientIP) { 92 | return plugin.NextOrFailure(vs.Name(), vs.Next, ctx, w, r) 93 | } else { 94 | hosts := make([]model.Instance, 0) 95 | host := vs.NacosClientImpl.SrvInstance(name[:len(name)-1], clientIP) 96 | hosts = append(hosts, *host) 97 | 98 | answer := make([]dns.RR, 0) 99 | extra := make([]dns.RR, 0) 100 | for _, host := range hosts { 101 | var rr dns.RR 102 | 103 | switch state.Family() { 104 | case 1: 105 | rr = new(dns.A) 106 | rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: state.QClass(), Ttl: DNSTTL} 107 | rr.(*dns.A).A = net.ParseIP(host.Ip).To4() 108 | case 2: 109 | rr = new(dns.AAAA) 110 | rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: state.QClass(), Ttl: DNSTTL} 111 | rr.(*dns.AAAA).AAAA = net.ParseIP(host.Ip) 112 | } 113 | 114 | srv := new(dns.SRV) 115 | srv.Hdr = dns.RR_Header{Name: "_" + state.Proto() + "." + state.QName(), Rrtype: dns.TypeSRV, Class: state.QClass(), Ttl: DNSTTL} 116 | port := host.Port 117 | srv.Port = uint16(port) 118 | srv.Target = "." 119 | 120 | extra = append(extra, srv) 121 | answer = append(answer, rr) 122 | } 123 | 124 | m.Answer = answer 125 | m.Extra = extra 126 | result, _ := json.Marshal(m.Answer) 127 | NacosClientLogger.Info("[RESOLVE]", " ["+name[:len(name)-1]+"] result: "+string(result)+", clientIP: "+clientIP) 128 | } 129 | 130 | m.SetReply(r) 131 | m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true 132 | 133 | state.SizeAndDo(m) 134 | m = state.Scrub(m) 135 | w.WriteMsg(m) 136 | return dns.RcodeSuccess, nil 137 | } 138 | 139 | func (vs *Nacos) Name() string { return "nacos" } 140 | -------------------------------------------------------------------------------- /nacos/nacos_grpc_client_test.go: -------------------------------------------------------------------------------- 1 | package nacos 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nacos-group/nacos-sdk-go/v2/model" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | var grpcClientTest = NewNacosGrpcClientTest() 11 | 12 | func NewNacosGrpcClientTest() *NacosGrpcClient { 13 | grpcClient, err := NewNacosGrpcClient("", []string{"console.nacos.io:8848"}, nacosClientTest) 14 | if err != nil { 15 | fmt.Println("init grpc client failed") 16 | } 17 | return grpcClient 18 | } 19 | 20 | func TestGetAllServicesInfo(t *testing.T) { 21 | services := grpcClientTest.GetAllServicesInfo() 22 | if len(services) > 0 { 23 | t.Log("GrpcClient get all servicesInfo passed") 24 | } else { 25 | t.Log("GrpcClient get all servicesInfo empty") 26 | } 27 | } 28 | 29 | func TestGetService(t *testing.T) { 30 | services := grpcClientTest.GetAllServicesInfo() 31 | serviceMap := NewConcurrentMap() 32 | for _, serviceName := range services { 33 | service := grpcClientTest.GetService(serviceName) 34 | if assert.NotNil(t, service) { 35 | serviceMap.Set(serviceName, service) 36 | } 37 | } 38 | if serviceMap.Count() == len(services) { 39 | t.Log("GrpcClient get service passed") 40 | } else { 41 | t.Error("GrpcClient get service error") 42 | } 43 | } 44 | 45 | func TestSubscribe(t *testing.T) { 46 | doms := grpcClientTest.GetAllServicesInfo() 47 | for _, dom := range doms { 48 | err := grpcClientTest.Subscribe(dom) 49 | if err != nil { 50 | t.Error("GrpcClient subscribe service error") 51 | return 52 | } 53 | } 54 | t.Log("GrpcClient subscribe service passed") 55 | } 56 | 57 | func TestCallback(t *testing.T) { 58 | services := model.Service{ 59 | Name: "DEFAULT_GROUP@@demo.go", 60 | CacheMillis: 1000, 61 | Hosts: []model.Instance{ 62 | { 63 | InstanceId: "10.10.10.10-80-a-DEMO", 64 | Port: 80, 65 | Ip: "10.10.10.10", 66 | Weight: 1, 67 | Metadata: map[string]string{}, 68 | ClusterName: "a", 69 | ServiceName: "DEFAULT_GROUP@@demo.go", 70 | Enable: true, 71 | Healthy: true, 72 | }, 73 | { 74 | InstanceId: "10.10.10.11-80-a-DEMO", 75 | Port: 80, 76 | Ip: "10.10.10.11", 77 | Weight: 1, 78 | Metadata: map[string]string{}, 79 | ClusterName: "a", 80 | ServiceName: "DEFAULT_GROUP@@demo.go", 81 | Enable: true, 82 | Healthy: true, 83 | }, 84 | { 85 | InstanceId: "10.10.10.12-80-a-DEMO", 86 | Port: 80, 87 | Ip: "10.10.10.12", 88 | Weight: 1, 89 | Metadata: map[string]string{}, 90 | ClusterName: "a", 91 | ServiceName: "DEFAULT_GROUP@@demo.go", 92 | Enable: true, 93 | Healthy: false, 94 | }, 95 | { 96 | InstanceId: "10.10.10.13-80-a-DEMO", 97 | Port: 80, 98 | Ip: "10.10.10.13", 99 | Weight: 1, 100 | Metadata: map[string]string{}, 101 | ClusterName: "a", 102 | ServiceName: "DEFAULT_GROUP@@demo.go", 103 | Enable: false, 104 | Healthy: true, 105 | }, 106 | { 107 | InstanceId: "10.10.10.14-80-a-DEMO", 108 | Port: 80, 109 | Ip: "10.10.10.14", 110 | Weight: 0, 111 | Metadata: map[string]string{}, 112 | ClusterName: "a", 113 | ServiceName: "DEFAULT_GROUP@@demo.go", 114 | Enable: true, 115 | Healthy: true, 116 | }, 117 | }, 118 | Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594", 119 | LastRefTime: 1528787794594, Clusters: "a"} 120 | 121 | grpcClientTest.nacosClient.GetDomainCache().Set("demo.go", services) 122 | 123 | newServices := model.Service{ 124 | Name: "DEFAULT_GROUP@@demo.go", 125 | CacheMillis: 1000, 126 | Hosts: []model.Instance{ 127 | { 128 | InstanceId: "10.10.10.10-80-a-DEMO", 129 | Port: 80, 130 | Ip: "10.10.10.10", 131 | Weight: 1, 132 | Metadata: map[string]string{}, 133 | ClusterName: "a", 134 | ServiceName: "DEFAULT_GROUP@@demo.go", 135 | Enable: true, 136 | Healthy: true, 137 | }, 138 | { 139 | InstanceId: "10.10.10.11-80-a-DEMO", 140 | Port: 80, 141 | Ip: "10.10.10.11", 142 | Weight: 1, 143 | Metadata: map[string]string{}, 144 | ClusterName: "a", 145 | ServiceName: "DEFAULT_GROUP@@demo.go", 146 | Enable: true, 147 | Healthy: true, 148 | }, 149 | { 150 | InstanceId: "10.10.10.12-80-a-DEMO", 151 | Port: 80, 152 | Ip: "10.10.10.12", 153 | Weight: 1, 154 | Metadata: map[string]string{}, 155 | ClusterName: "a", 156 | ServiceName: "DEFAULT_GROUP@@demo.go", 157 | Enable: true, 158 | Healthy: false, 159 | }, 160 | { 161 | InstanceId: "10.10.10.13-80-a-DEMO", 162 | Port: 80, 163 | Ip: "10.10.10.13", 164 | Weight: 1, 165 | Metadata: map[string]string{}, 166 | ClusterName: "a", 167 | ServiceName: "DEFAULT_GROUP@@demo.go", 168 | Enable: false, 169 | Healthy: true, 170 | }, 171 | }, 172 | Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594", 173 | LastRefTime: 1528787794594, Clusters: "a"} 174 | grpcClientTest.Callback(newServices.Hosts, nil) 175 | 176 | s, _ := grpcClientTest.nacosClient.GetDomainCache().Get("demo.go") 177 | 178 | updateServices := s.(model.Service) 179 | 180 | if len(newServices.Hosts) == len(updateServices.Hosts) { 181 | t.Log("GrpcClient Service SubscribeCallback passed") 182 | } else { 183 | t.Error("GrpcClient Service SubscribeCallback error") 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /nacos/nacos_grpc_client.go: -------------------------------------------------------------------------------- 1 | package nacos 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nacos-group/nacos-sdk-go/v2/clients" 6 | "github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client" 7 | "github.com/nacos-group/nacos-sdk-go/v2/common/constant" 8 | "github.com/nacos-group/nacos-sdk-go/v2/model" 9 | "github.com/nacos-group/nacos-sdk-go/v2/vo" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | type NacosGrpcClient struct { 16 | namespaceId string 17 | clientConfig constant.ClientConfig //nacos-coredns客户端配置 18 | serverConfigs []constant.ServerConfig //nacos服务器集群配置 19 | grpcClient naming_client.INamingClient //nacos-coredns与nacos服务器的grpc连接 20 | nacosClient *NacosClient 21 | SubscribeMap AllDomsMap 22 | } 23 | 24 | func NewNacosGrpcClient(namespaceId string, serverHosts []string, vc *NacosClient) (*NacosGrpcClient, error) { 25 | var nacosGrpcClient NacosGrpcClient 26 | nacosGrpcClient.nacosClient = vc 27 | if namespaceId == "public" { 28 | namespaceId = "" 29 | } 30 | nacosGrpcClient.namespaceId = namespaceId //When namespace is public, fill in the blank string here. 31 | 32 | serverConfigs := make([]constant.ServerConfig, len(serverHosts)) 33 | for i, serverHost := range serverHosts { 34 | serverIp := strings.Split(serverHost, ":")[0] 35 | serverPort, err := strconv.Atoi(strings.Split(serverHost, ":")[1]) 36 | if err != nil { 37 | NacosClientLogger.Error("nacos server host config error!", err) 38 | } 39 | serverConfigs[i] = *constant.NewServerConfig( 40 | serverIp, 41 | uint64(serverPort), 42 | constant.WithScheme("http"), 43 | constant.WithContextPath("/nacos"), 44 | ) 45 | 46 | } 47 | nacosGrpcClient.serverConfigs = serverConfigs 48 | 49 | nacosGrpcClient.clientConfig = *constant.NewClientConfig( 50 | constant.WithNamespaceId(namespaceId), 51 | constant.WithTimeoutMs(5000), 52 | constant.WithNotLoadCacheAtStart(true), 53 | constant.WithUpdateCacheWhenEmpty(true), 54 | constant.WithLogDir(LogPath), 55 | constant.WithCacheDir(CachePath), 56 | constant.WithLogLevel("debug"), 57 | ) 58 | 59 | var err error 60 | nacosGrpcClient.grpcClient, err = clients.NewNamingClient( 61 | vo.NacosClientParam{ 62 | ClientConfig: &nacosGrpcClient.clientConfig, 63 | ServerConfigs: nacosGrpcClient.serverConfigs, 64 | }, 65 | ) 66 | if err != nil { 67 | fmt.Println("init nacos-client error") 68 | } 69 | nacosGrpcClient.SubscribeMap = AllDomsMap{} 70 | nacosGrpcClient.SubscribeMap.Data = make(map[string]bool) 71 | nacosGrpcClient.SubscribeMap.DLock = sync.RWMutex{} 72 | 73 | return &nacosGrpcClient, err 74 | } 75 | 76 | func (ngc *NacosGrpcClient) GetAllServicesInfo() []string { 77 | var pageNo = uint32(1) 78 | var pageSize = uint32(100) 79 | var services []string 80 | 81 | pageServiceList, _ := ngc.grpcClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{ 82 | NameSpace: ngc.namespaceId, 83 | PageNo: pageNo, 84 | PageSize: pageSize, 85 | }) 86 | services = append(services, pageServiceList.Doms...) 87 | 88 | // 如果当前页数服务数满了, 继续查找添加下一页 89 | for pageNo++; len(pageServiceList.Doms) >= int(pageSize); pageNo++ { 90 | pageServiceList, _ = ngc.grpcClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{ 91 | NameSpace: ngc.namespaceId, 92 | PageNo: pageNo, 93 | PageSize: pageSize, 94 | }) 95 | services = append(services, pageServiceList.Doms...) 96 | } 97 | return services 98 | 99 | } 100 | 101 | func (ngc *NacosGrpcClient) GetService(serviceName string) model.Service { 102 | service, _ := ngc.grpcClient.GetService(vo.GetServiceParam{ 103 | ServiceName: serviceName, 104 | }) 105 | if service.Hosts == nil { 106 | NacosClientLogger.Warn("empty result from server, dom:" + serviceName) 107 | } 108 | 109 | return service 110 | } 111 | 112 | func (ngc *NacosGrpcClient) Subscribe(serviceName string) error { 113 | if ngc.HasSubcribed(serviceName) { 114 | NacosClientLogger.Info("service " + serviceName + " already subsrcibed.") 115 | return nil 116 | } 117 | param := &vo.SubscribeParam{ 118 | ServiceName: serviceName, 119 | GroupName: "", 120 | SubscribeCallback: ngc.Callback, 121 | } 122 | if err := ngc.grpcClient.Subscribe(param); err != nil { 123 | NacosClientLogger.Error("service subscribe error " + serviceName) 124 | return err 125 | } 126 | 127 | defer ngc.SubscribeMap.DLock.Unlock() 128 | ngc.SubscribeMap.DLock.Lock() 129 | ngc.SubscribeMap.Data[serviceName] = true 130 | 131 | return nil 132 | } 133 | 134 | func (ngc *NacosGrpcClient) Unsubsrcibe(serviceName string) error { 135 | if !ngc.HasSubcribed(serviceName) { 136 | NacosClientLogger.Info("service " + serviceName + " already unsubsrcibed.") 137 | return nil 138 | } 139 | param := &vo.SubscribeParam{ 140 | ServiceName: serviceName, 141 | GroupName: "", 142 | SubscribeCallback: ngc.Callback, 143 | } 144 | if err := ngc.grpcClient.Unsubscribe(param); err != nil { 145 | NacosClientLogger.Error("service unsubscribe error " + serviceName) 146 | return err 147 | } 148 | 149 | defer ngc.SubscribeMap.DLock.Unlock() 150 | ngc.SubscribeMap.DLock.Lock() 151 | ngc.SubscribeMap.Data[serviceName] = false 152 | 153 | return nil 154 | } 155 | 156 | func (ngc *NacosGrpcClient) Callback(instances []model.Instance, err error) { 157 | //服务下线,更新实例数量为0 158 | if len(instances) == 0 { 159 | for serviceName, _ := range AllDoms.Data { 160 | if service := ngc.GetService(serviceName); len(service.Hosts) == 0 { 161 | ngc.nacosClient.GetDomainCache().Set(serviceName, service) 162 | ngc.Unsubsrcibe(serviceName) 163 | } 164 | } 165 | return 166 | } 167 | 168 | serviceName := strings.Split(instances[0].ServiceName, SEPERATOR)[1] 169 | oldService, ok := ngc.nacosClient.GetDomainCache().Get(serviceName) 170 | if !ok { 171 | NacosClientLogger.Info("service not found in cache " + serviceName) 172 | service := ngc.GetService(serviceName) 173 | ngc.nacosClient.GetDomainCache().Set(serviceName, service) 174 | } else { 175 | service := oldService.(model.Service) 176 | service.Hosts = instances 177 | service.LastRefTime = uint64(CurrentMillis()) 178 | ngc.nacosClient.GetDomainCache().Set(serviceName, service) 179 | } 180 | NacosClientLogger.Info("serviceName: "+serviceName+" was updated to: ", instances) 181 | 182 | } 183 | 184 | func (ngc *NacosGrpcClient) HasSubcribed(serviceName string) bool { 185 | defer ngc.SubscribeMap.DLock.RUnlock() 186 | ngc.SubscribeMap.DLock.RLock() 187 | return ngc.SubscribeMap.Data[serviceName] 188 | } 189 | -------------------------------------------------------------------------------- /forward/setup.go: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/coredns/caddy" 11 | "github.com/coredns/coredns/core/dnsserver" 12 | "github.com/coredns/coredns/plugin" 13 | "github.com/coredns/coredns/plugin/dnstap" 14 | "github.com/coredns/coredns/plugin/pkg/parse" 15 | pkgtls "github.com/coredns/coredns/plugin/pkg/tls" 16 | "github.com/coredns/coredns/plugin/pkg/transport" 17 | ) 18 | 19 | func init() { plugin.Register("forward", setup) } 20 | 21 | func setup(c *caddy.Controller) error { 22 | f, err := parseForward(c) 23 | if err != nil { 24 | return plugin.Error("forward", err) 25 | } 26 | if f.Len() > max { 27 | return plugin.Error("forward", fmt.Errorf("more than %d TOs configured: %d", max, f.Len())) 28 | } 29 | 30 | dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { 31 | f.Next = next 32 | return f 33 | }) 34 | 35 | c.OnStartup(func() error { 36 | return f.OnStartup() 37 | }) 38 | c.OnStartup(func() error { 39 | if taph := dnsserver.GetConfig(c).Handler("dnstap"); taph != nil { 40 | if tapPlugin, ok := taph.(dnstap.Dnstap); ok { 41 | f.tapPlugin = &tapPlugin 42 | } 43 | } 44 | return nil 45 | }) 46 | 47 | c.OnShutdown(func() error { 48 | return f.OnShutdown() 49 | }) 50 | 51 | return nil 52 | } 53 | 54 | // OnStartup starts a goroutines for all proxies. 55 | func (f *Forward) OnStartup() (err error) { 56 | for _, p := range f.proxies { 57 | p.start(f.hcInterval) 58 | } 59 | return nil 60 | } 61 | 62 | // OnShutdown stops all configured proxies. 63 | func (f *Forward) OnShutdown() error { 64 | for _, p := range f.proxies { 65 | p.stop() 66 | } 67 | return nil 68 | } 69 | 70 | func parseForward(c *caddy.Controller) (*Forward, error) { 71 | var ( 72 | f *Forward 73 | err error 74 | i int 75 | ) 76 | for c.Next() { 77 | if i > 0 { 78 | return nil, plugin.ErrOnce 79 | } 80 | i++ 81 | f, err = parseStanza(c) 82 | if err != nil { 83 | return nil, err 84 | } 85 | } 86 | return f, nil 87 | } 88 | 89 | func parseStanza(c *caddy.Controller) (*Forward, error) { 90 | f := New() 91 | 92 | if !c.Args(&f.from) { 93 | return f, c.ArgErr() 94 | } 95 | origFrom := f.from 96 | zones := plugin.Host(f.from).NormalizeExact() 97 | if len(zones) == 0 { 98 | return f, fmt.Errorf("unable to normalize '%s'", f.from) 99 | } 100 | f.from = zones[0] // there can only be one here, won't work with non-octet reverse 101 | 102 | if len(zones) > 1 { 103 | log.Warningf("Unsupported CIDR notation: '%s' expands to multiple zones. Using only '%s'.", origFrom, f.from) 104 | } 105 | 106 | to := c.RemainingArgs() 107 | if len(to) == 0 { 108 | return f, c.ArgErr() 109 | } 110 | 111 | toHosts, err := parse.HostPortOrFile(to...) 112 | if err != nil { 113 | return f, err 114 | } 115 | 116 | transports := make([]string, len(toHosts)) 117 | allowedTrans := map[string]bool{"dns": true, "tls": true} 118 | for i, host := range toHosts { 119 | //skip local nameserver 120 | if host == "127.0.0.1:53" { 121 | continue 122 | } 123 | 124 | trans, h := parse.Transport(host) 125 | 126 | if !allowedTrans[trans] { 127 | return f, fmt.Errorf("'%s' is not supported as a destination protocol in forward: %s", trans, host) 128 | } 129 | p := NewProxy(h, trans) 130 | f.proxies = append(f.proxies, p) 131 | transports[i] = trans 132 | } 133 | 134 | for c.NextBlock() { 135 | if err := parseBlock(c, f); err != nil { 136 | return f, err 137 | } 138 | } 139 | 140 | if f.tlsServerName != "" { 141 | f.tlsConfig.ServerName = f.tlsServerName 142 | } 143 | 144 | // Initialize ClientSessionCache in tls.Config. This may speed up a TLS handshake 145 | // in upcoming connections to the same TLS server. 146 | f.tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(len(f.proxies)) 147 | 148 | for i := range f.proxies { 149 | // Only set this for proxies that need it. 150 | if transports[i] == transport.TLS { 151 | f.proxies[i].SetTLSConfig(f.tlsConfig) 152 | } 153 | f.proxies[i].SetExpire(f.expire) 154 | f.proxies[i].health.SetRecursionDesired(f.opts.hcRecursionDesired) 155 | // when TLS is used, checks are set to tcp-tls 156 | if f.opts.forceTCP && transports[i] != transport.TLS { 157 | f.proxies[i].health.SetTCPTransport() 158 | } 159 | f.proxies[i].health.SetDomain(f.opts.hcDomain) 160 | } 161 | 162 | return f, nil 163 | } 164 | 165 | func parseBlock(c *caddy.Controller, f *Forward) error { 166 | switch c.Val() { 167 | case "except": 168 | ignore := c.RemainingArgs() 169 | if len(ignore) == 0 { 170 | return c.ArgErr() 171 | } 172 | for i := 0; i < len(ignore); i++ { 173 | f.ignored = append(f.ignored, plugin.Host(ignore[i]).NormalizeExact()...) 174 | } 175 | case "max_fails": 176 | if !c.NextArg() { 177 | return c.ArgErr() 178 | } 179 | n, err := strconv.ParseUint(c.Val(), 10, 32) 180 | if err != nil { 181 | return err 182 | } 183 | f.maxfails = uint32(n) 184 | case "health_check": 185 | if !c.NextArg() { 186 | return c.ArgErr() 187 | } 188 | dur, err := time.ParseDuration(c.Val()) 189 | if err != nil { 190 | return err 191 | } 192 | if dur < 0 { 193 | return fmt.Errorf("health_check can't be negative: %d", dur) 194 | } 195 | f.hcInterval = dur 196 | f.opts.hcDomain = "." 197 | 198 | for c.NextArg() { 199 | switch hcOpts := c.Val(); hcOpts { 200 | case "no_rec": 201 | f.opts.hcRecursionDesired = false 202 | case "domain": 203 | if !c.NextArg() { 204 | return c.ArgErr() 205 | } 206 | f.opts.hcDomain = c.Val() 207 | default: 208 | return fmt.Errorf("health_check: unknown option %s", hcOpts) 209 | } 210 | } 211 | 212 | case "force_tcp": 213 | if c.NextArg() { 214 | return c.ArgErr() 215 | } 216 | f.opts.forceTCP = true 217 | case "prefer_udp": 218 | if c.NextArg() { 219 | return c.ArgErr() 220 | } 221 | f.opts.preferUDP = true 222 | case "tls": 223 | args := c.RemainingArgs() 224 | if len(args) > 3 { 225 | return c.ArgErr() 226 | } 227 | 228 | tlsConfig, err := pkgtls.NewTLSConfigFromArgs(args...) 229 | if err != nil { 230 | return err 231 | } 232 | f.tlsConfig = tlsConfig 233 | case "tls_servername": 234 | if !c.NextArg() { 235 | return c.ArgErr() 236 | } 237 | f.tlsServerName = c.Val() 238 | case "expire": 239 | if !c.NextArg() { 240 | return c.ArgErr() 241 | } 242 | dur, err := time.ParseDuration(c.Val()) 243 | if err != nil { 244 | return err 245 | } 246 | if dur < 0 { 247 | return fmt.Errorf("expire can't be negative: %s", dur) 248 | } 249 | f.expire = dur 250 | case "policy": 251 | if !c.NextArg() { 252 | return c.ArgErr() 253 | } 254 | switch x := c.Val(); x { 255 | case "random": 256 | f.p = &random{} 257 | case "round_robin": 258 | f.p = &roundRobin{} 259 | case "sequential": 260 | f.p = &sequential{} 261 | default: 262 | return c.Errf("unknown policy '%s'", x) 263 | } 264 | case "max_concurrent": 265 | if !c.NextArg() { 266 | return c.ArgErr() 267 | } 268 | n, err := strconv.Atoi(c.Val()) 269 | if err != nil { 270 | return err 271 | } 272 | if n < 0 { 273 | return fmt.Errorf("max_concurrent can't be negative: %d", n) 274 | } 275 | f.ErrLimitExceeded = errors.New("concurrent queries exceeded maximum " + c.Val()) 276 | f.maxConcurrent = int64(n) 277 | 278 | default: 279 | return c.Errf("unknown property '%s'", c.Val()) 280 | } 281 | 282 | return nil 283 | } 284 | 285 | const max = 15 // Maximum number of upstreams. 286 | -------------------------------------------------------------------------------- /nacos/concurrent_map.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "encoding/json" 18 | "sync" 19 | ) 20 | 21 | var SHARD_COUNT = 32 22 | 23 | // A "thread" safe map of type string:Anything. 24 | // To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. 25 | type ConcurrentMap []*ConcurrentMapShared 26 | 27 | // A "thread" safe string to anything map. 28 | type ConcurrentMapShared struct { 29 | items map[string]interface{} 30 | sync.RWMutex // Read Write mutex, guards access to internal map. 31 | } 32 | 33 | // Creates a new concurrent map. 34 | func NewConcurrentMap() ConcurrentMap { 35 | m := make(ConcurrentMap, SHARD_COUNT) 36 | for i := 0; i < SHARD_COUNT; i++ { 37 | m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} 38 | } 39 | return m 40 | } 41 | 42 | // Returns shard under given key 43 | func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared { 44 | return m[uint(fnv32(key))%uint(SHARD_COUNT)] 45 | } 46 | 47 | func (m ConcurrentMap) MSet(data map[string]interface{}) { 48 | for key, value := range data { 49 | shard := m.GetShard(key) 50 | shard.Lock() 51 | shard.items[key] = value 52 | shard.Unlock() 53 | } 54 | } 55 | 56 | // Sets the given value under the specified key. 57 | func (m ConcurrentMap) Set(key string, value interface{}) { 58 | // Get map shard. 59 | shard := m.GetShard(key) 60 | shard.Lock() 61 | shard.items[key] = value 62 | shard.Unlock() 63 | } 64 | 65 | // Callback to return new element to be inserted into the map 66 | // It is called while lock is held, therefore it MUST NOT 67 | // try to access other keys in same map, as it can lead to deadlock since 68 | // Go sync.RWLock is not reentrant 69 | type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{} 70 | 71 | // Insert or Update - updates existing element or inserts a new one using UpsertCb 72 | func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) { 73 | shard := m.GetShard(key) 74 | shard.Lock() 75 | v, ok := shard.items[key] 76 | res = cb(ok, v, value) 77 | shard.items[key] = res 78 | shard.Unlock() 79 | return res 80 | } 81 | 82 | // Sets the given value under the specified key if no value was associated with it. 83 | func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { 84 | // Get map shard. 85 | shard := m.GetShard(key) 86 | shard.Lock() 87 | _, ok := shard.items[key] 88 | if !ok { 89 | shard.items[key] = value 90 | } 91 | shard.Unlock() 92 | return !ok 93 | } 94 | 95 | // Retrieves an element from map under given key. 96 | func (m ConcurrentMap) Get(key string) (interface{}, bool) { 97 | // Get shard 98 | shard := m.GetShard(key) 99 | shard.RLock() 100 | // Get item from shard. 101 | val, ok := shard.items[key] 102 | shard.RUnlock() 103 | return val, ok 104 | } 105 | 106 | // Returns the number of elements within the map. 107 | func (m ConcurrentMap) Count() int { 108 | count := 0 109 | for i := 0; i < SHARD_COUNT; i++ { 110 | shard := m[i] 111 | shard.RLock() 112 | count += len(shard.items) 113 | shard.RUnlock() 114 | } 115 | return count 116 | } 117 | 118 | // Looks up an item under specified key 119 | func (m ConcurrentMap) Has(key string) bool { 120 | // Get shard 121 | shard := m.GetShard(key) 122 | shard.RLock() 123 | // See if element is within shard. 124 | _, ok := shard.items[key] 125 | shard.RUnlock() 126 | return ok 127 | } 128 | 129 | // Removes an element from the map. 130 | func (m ConcurrentMap) Remove(key string) { 131 | // Try to get shard. 132 | shard := m.GetShard(key) 133 | shard.Lock() 134 | delete(shard.items, key) 135 | shard.Unlock() 136 | } 137 | 138 | // Removes an element from the map and returns it 139 | func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) { 140 | // Try to get shard. 141 | shard := m.GetShard(key) 142 | shard.Lock() 143 | v, exists = shard.items[key] 144 | delete(shard.items, key) 145 | shard.Unlock() 146 | return v, exists 147 | } 148 | 149 | // Checks if map is empty. 150 | func (m ConcurrentMap) IsEmpty() bool { 151 | return m.Count() == 0 152 | } 153 | 154 | // Used by the Iter & IterBuffered functions to wrap two variables together over a channel, 155 | type Tuple struct { 156 | Key string 157 | Val interface{} 158 | } 159 | 160 | // Returns an iterator which could be used in a for range loop. 161 | // 162 | // Deprecated: using IterBuffered() will get a better performence 163 | func (m ConcurrentMap) Iter() <-chan Tuple { 164 | chans := snapshot(m) 165 | ch := make(chan Tuple) 166 | go fanIn(chans, ch) 167 | return ch 168 | } 169 | 170 | // Returns a buffered iterator which could be used in a for range loop. 171 | func (m ConcurrentMap) IterBuffered() <-chan Tuple { 172 | chans := snapshot(m) 173 | total := 0 174 | for _, c := range chans { 175 | total += cap(c) 176 | } 177 | ch := make(chan Tuple, total) 178 | go fanIn(chans, ch) 179 | return ch 180 | } 181 | 182 | // Returns a array of channels that contains elements in each shard, 183 | // which likely takes a snapshot of `m`. 184 | // It returns once the size of each buffered channel is determined, 185 | // before all the channels are populated using goroutines. 186 | func snapshot(m ConcurrentMap) (chans []chan Tuple) { 187 | chans = make([]chan Tuple, SHARD_COUNT) 188 | wg := sync.WaitGroup{} 189 | wg.Add(SHARD_COUNT) 190 | // Foreach shard. 191 | for index, shard := range m { 192 | go func(index int, shard *ConcurrentMapShared) { 193 | // Foreach key, value pair. 194 | shard.RLock() 195 | chans[index] = make(chan Tuple, len(shard.items)) 196 | wg.Done() 197 | for key, val := range shard.items { 198 | chans[index] <- Tuple{key, val} 199 | } 200 | shard.RUnlock() 201 | close(chans[index]) 202 | }(index, shard) 203 | } 204 | wg.Wait() 205 | return chans 206 | } 207 | 208 | // fanIn reads elements from channels `chans` into channel `out` 209 | func fanIn(chans []chan Tuple, out chan Tuple) { 210 | wg := sync.WaitGroup{} 211 | wg.Add(len(chans)) 212 | for _, ch := range chans { 213 | go func(ch chan Tuple) { 214 | for t := range ch { 215 | out <- t 216 | } 217 | wg.Done() 218 | }(ch) 219 | } 220 | wg.Wait() 221 | close(out) 222 | } 223 | 224 | // Returns all items as map[string]interface{} 225 | func (m ConcurrentMap) Items() map[string]interface{} { 226 | tmp := make(map[string]interface{}) 227 | 228 | // Insert items to temporary map. 229 | for item := range m.IterBuffered() { 230 | tmp[item.Key] = item.Val 231 | } 232 | 233 | return tmp 234 | } 235 | 236 | // Iterator callback,called for every key,value found in 237 | // maps. RLock is held for all calls for a given shard 238 | // therefore callback sess consistent view of a shard, 239 | // but not across the shards 240 | type IterCb func(key string, v interface{}) 241 | 242 | // Callback based iterator, cheapest way to read 243 | // all elements in a map. 244 | func (m ConcurrentMap) IterCb(fn IterCb) { 245 | for idx := range m { 246 | shard := (m)[idx] 247 | shard.RLock() 248 | for key, value := range shard.items { 249 | fn(key, value) 250 | } 251 | shard.RUnlock() 252 | } 253 | } 254 | 255 | // Return all keys as []string 256 | func (m ConcurrentMap) Keys() []string { 257 | count := m.Count() 258 | ch := make(chan string, count) 259 | go func() { 260 | // Foreach shard. 261 | wg := sync.WaitGroup{} 262 | wg.Add(SHARD_COUNT) 263 | for _, shard := range m { 264 | go func(shard *ConcurrentMapShared) { 265 | // Foreach key, value pair. 266 | shard.RLock() 267 | for key := range shard.items { 268 | ch <- key 269 | } 270 | shard.RUnlock() 271 | wg.Done() 272 | }(shard) 273 | } 274 | wg.Wait() 275 | close(ch) 276 | }() 277 | 278 | // Generate keys 279 | keys := make([]string, 0, count) 280 | for k := range ch { 281 | keys = append(keys, k) 282 | } 283 | return keys 284 | } 285 | 286 | //Reviles ConcurrentMap "private" variables to json marshal. 287 | func (m ConcurrentMap) MarshalJSON() ([]byte, error) { 288 | // Create a temporary map, which will hold all item spread across shards. 289 | tmp := make(map[string]interface{}) 290 | 291 | // Insert items to temporary map. 292 | for item := range m.IterBuffered() { 293 | tmp[item.Key] = item.Val 294 | } 295 | return json.Marshal(tmp) 296 | } 297 | 298 | func fnv32(key string) uint32 { 299 | hash := uint32(2166136261) 300 | const prime32 = uint32(16777619) 301 | for i := 0; i < len(key); i++ { 302 | hash *= prime32 303 | hash ^= uint32(key[i]) 304 | } 305 | return hash 306 | } 307 | -------------------------------------------------------------------------------- /nacos/nacos_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package nacos 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "github.com/nacos-group/nacos-sdk-go/v2/model" 20 | "io/ioutil" 21 | "math" 22 | "math/rand" 23 | "os" 24 | "path/filepath" 25 | "reflect" 26 | "strconv" 27 | "strings" 28 | "sync" 29 | "time" 30 | 31 | "github.com/cihub/seelog" 32 | ) 33 | 34 | var ( 35 | NacosClientLogger seelog.LoggerInterface 36 | LogConfig string 37 | GrpcClient *NacosGrpcClient 38 | ) 39 | 40 | func init() { 41 | initLog() 42 | } 43 | 44 | type NacosClient struct { 45 | serviceMap ConcurrentMap 46 | udpServer UDPServer 47 | } 48 | 49 | type NacosClientError struct { 50 | Msg string 51 | } 52 | 53 | func (err NacosClientError) Error() string { 54 | return err.Msg 55 | } 56 | 57 | var Inited = false 58 | 59 | func exists(path string) (bool, error) { 60 | _, err := os.Stat(path) 61 | if err == nil { 62 | return true, nil 63 | } 64 | if os.IsNotExist(err) { 65 | return false, nil 66 | } 67 | return true, err 68 | } 69 | 70 | func initDir() { 71 | dir, err := filepath.Abs(Home()) 72 | if err != nil { 73 | os.Exit(1) 74 | } 75 | 76 | if LogPath == "" { 77 | LogPath = dir + string(os.PathSeparator) + "logs" 78 | } 79 | 80 | if CachePath == "" { 81 | CachePath = dir + string(os.PathSeparator) + "nacos-go-client-cache" 82 | } 83 | 84 | mkdirIfNecessary(CachePath) 85 | } 86 | 87 | func mkdirIfNecessary(path string) { 88 | if ok, _ := exists(path); !ok { 89 | err := os.Mkdir(path, 0755) 90 | if err != nil { 91 | NacosClientLogger.Warn("can not cread dir: "+path, err) 92 | } 93 | } 94 | } 95 | 96 | func initLog() { 97 | if NacosClientLogger != nil { 98 | return 99 | } 100 | 101 | initDir() 102 | var err error 103 | var nacosLogger seelog.LoggerInterface 104 | if LogConfig == "" || !Exist(LogConfig) { 105 | home := Home() 106 | LogConfig = home 107 | nacosLogger, err = seelog.LoggerFromConfigAsString(` 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | `) 119 | } else { 120 | nacosLogger, err = seelog.LoggerFromConfigAsFile(LogConfig) 121 | } 122 | 123 | fmt.Println("log directory: " + LogConfig + "/logs/nacos-go-client/") 124 | 125 | if err != nil { 126 | fmt.Println("Failed to init log, ", err) 127 | } else { 128 | NacosClientLogger = nacosLogger 129 | } 130 | } 131 | 132 | func (nacosClient *NacosClient) asyncGetAllServiceNames() { 133 | for { 134 | time.Sleep(time.Duration(AllDoms.CacheSeconds) * time.Second) 135 | nacosClient.getAllServiceNames() 136 | } 137 | } 138 | 139 | //func (nacosClient *NacosClient) GetServerManager() (serverManager *ServerManager) { 140 | // return &nacosClient.serverManager 141 | //} 142 | 143 | func (nacosClient *NacosClient) GetUdpServer() (us UDPServer) { 144 | return nacosClient.udpServer 145 | } 146 | 147 | func (nacosClient *NacosClient) getAllServiceNames() { 148 | 149 | services := GrpcClient.GetAllServicesInfo() 150 | if services == nil { 151 | NacosClientLogger.Warn("No Service return from servers.") 152 | return 153 | } 154 | 155 | AllDoms.DLock.Lock() 156 | if AllDoms.Data == nil { 157 | allDoms := make(map[string]bool) 158 | // record all serviceNames return from server 159 | for _, service := range services { 160 | allDoms[service] = true 161 | } 162 | AllDoms.Data = allDoms 163 | AllDoms.CacheSeconds = 20 //刷新间隔 164 | } else { 165 | for _, service := range services { 166 | if !AllDoms.Data[service] { 167 | AllDoms.Data[service] = true 168 | } 169 | } 170 | } 171 | AllDoms.DLock.Unlock() 172 | } 173 | 174 | //func (nacosClient *NacosClient) SetServers(servers []string) { 175 | // nacosClient.serverManager.SetServers(servers) 176 | //} 177 | 178 | func (vc *NacosClient) Registered(service string) bool { 179 | defer AllDoms.DLock.RUnlock() 180 | AllDoms.DLock.RLock() 181 | _, ok1 := AllDoms.Data[service] 182 | 183 | return ok1 184 | } 185 | 186 | func (vc *NacosClient) loadCache() { 187 | NacosSdkCachePath := CachePath + "/naming/public/" 188 | files, err := ioutil.ReadDir(NacosSdkCachePath) 189 | if err != nil { 190 | NacosClientLogger.Critical(err) 191 | } 192 | 193 | for _, f := range files { 194 | fileName := NacosSdkCachePath + string(os.PathSeparator) + f.Name() 195 | b, err := ioutil.ReadFile(fileName) 196 | if err != nil { 197 | NacosClientLogger.Error("failed to read cache file: "+fileName, err) 198 | } 199 | 200 | s := string(b) 201 | var service model.Service 202 | err1 := json.Unmarshal([]byte(s), &service) 203 | 204 | if err1 != nil { 205 | continue 206 | } 207 | 208 | vc.serviceMap.Set(f.Name(), service) 209 | } 210 | 211 | NacosClientLogger.Info("finish loading cache, total: " + strconv.Itoa(len(files))) 212 | } 213 | 214 | func ProcessDomainString(s string) (model.Service, error) { 215 | var service model.Service 216 | err1 := json.Unmarshal([]byte(s), &service) 217 | 218 | if err1 != nil { 219 | NacosClientLogger.Error("failed to unmarshal json string: "+s, err1) 220 | return model.Service{}, err1 221 | } 222 | 223 | if len(service.Hosts) == 0 { 224 | NacosClientLogger.Warn("get empty ip list, ignore it, service: " + service.Name) 225 | return service, NacosClientError{"empty ip list"} 226 | } 227 | 228 | NacosClientLogger.Info("domain "+service.Name+" is updated, current ips: ", service.Hosts) 229 | return service, nil 230 | } 231 | 232 | func NewNacosClient(namespaceId string, serverHosts []string) *NacosClient { 233 | fmt.Println("init nacos client.") 234 | initLog() 235 | vc := NacosClient{NewConcurrentMap(), UDPServer{}} 236 | vc.loadCache() 237 | vc.udpServer.vipClient = &vc 238 | //init grpcClient 239 | var err error 240 | GrpcClient, err = NewNacosGrpcClient(namespaceId, serverHosts, &vc) 241 | if err != nil { 242 | NacosClientLogger.Error("init nacos-grpc-client failed.", err) 243 | } 244 | 245 | if EnableReceivePush { 246 | go vc.udpServer.StartServer() 247 | } 248 | 249 | AllDoms = AllDomsMap{} 250 | AllDoms.Data = make(map[string]bool) 251 | AllDoms.DLock = sync.RWMutex{} 252 | AllDoms.CacheSeconds = 20 253 | 254 | vc.getAllServiceNames() 255 | 256 | go vc.asyncGetAllServiceNames() 257 | 258 | //go vc.asyncUpdateDomain() 259 | 260 | NacosClientLogger.Info("cache-path: " + CachePath) 261 | return &vc 262 | } 263 | 264 | func (vc *NacosClient) GetDomainCache() ConcurrentMap { 265 | return vc.serviceMap 266 | } 267 | 268 | func (vc *NacosClient) GetDomain(name string) (*Domain, error) { 269 | item, _ := vc.serviceMap.Get(name) 270 | 271 | if item == nil { 272 | domain := Domain{} 273 | ss := strings.Split(name, SEPERATOR) 274 | 275 | domain.Name = ss[0] 276 | domain.CacheMillis = DefaultCacheMillis 277 | domain.LastRefMillis = CurrentMillis() 278 | vc.serviceMap.Set(name, domain) 279 | item = domain 280 | return nil, NacosClientError{"domain not found: " + name} 281 | } 282 | 283 | domain := item.(Domain) 284 | 285 | return &domain, nil 286 | } 287 | 288 | func (vc *NacosClient) asyncUpdateDomain() { 289 | for { 290 | for k, v := range vc.serviceMap.Items() { 291 | service := v.(model.Service) 292 | ss := strings.Split(k, SEPERATOR) 293 | 294 | serviceName := ss[0] 295 | var clientIP string 296 | if len(ss) > 1 && ss[1] != "" { 297 | clientIP = ss[1] 298 | } 299 | 300 | if uint64(CurrentMillis())-service.LastRefTime > service.CacheMillis && vc.Registered(serviceName) { 301 | vc.getServiceNow(serviceName, &vc.serviceMap, clientIP) 302 | } 303 | } 304 | time.Sleep(1 * time.Second) 305 | } 306 | 307 | } 308 | 309 | func GetCacheKey(dom, clientIP string) string { 310 | return dom + SEPERATOR + clientIP 311 | } 312 | 313 | func (vc *NacosClient) getServiceNow(serviceName string, cache *ConcurrentMap, clientIP string) model.Service { 314 | service := GrpcClient.GetService(serviceName) 315 | 316 | cache.Set(serviceName, service) 317 | 318 | NacosClientLogger.Info("dom "+serviceName+" updated: ", service) 319 | 320 | return service 321 | } 322 | 323 | func (vc *NacosClient) SrvInstance(serviceName, clientIP string) *model.Instance { 324 | item, hasService := vc.serviceMap.Get(serviceName) 325 | var service model.Service 326 | if !hasService { 327 | service = vc.getServiceNow(serviceName, &vc.serviceMap, clientIP) 328 | vc.serviceMap.Set(serviceName, service) 329 | } else { 330 | service = item.(model.Service) 331 | } 332 | 333 | //select healthy instances 334 | var hosts []model.Instance 335 | for _, host := range service.Hosts { 336 | if host.Healthy && host.Enable && host.Weight > 0 { 337 | hosts = append(hosts, host) 338 | } 339 | } 340 | 341 | if len(hosts) == 0 { 342 | NacosClientLogger.Warn("no healthy instances for " + serviceName) 343 | return nil 344 | } 345 | 346 | i, indexOk := indexMap.Get(serviceName) 347 | var index int 348 | 349 | if !indexOk { 350 | index = rand.Intn(len(hosts)) 351 | } else { 352 | index = i.(int) 353 | index += 1 354 | if index >= len(hosts) { 355 | index = index % len(hosts) 356 | } 357 | } 358 | 359 | indexMap.Set(serviceName, index) 360 | 361 | return &hosts[index] 362 | } 363 | 364 | func (vc *NacosClient) SrvInstances(domainName, clientIP string) []model.Instance { 365 | cacheKey := GetCacheKey(domainName, clientIP) 366 | item, hasDom := vc.serviceMap.Get(cacheKey) 367 | var dom model.Service 368 | 369 | if !hasDom { 370 | dom = vc.getServiceNow(domainName, &vc.serviceMap, clientIP) 371 | vc.serviceMap.Set(cacheKey, dom) 372 | } else { 373 | dom = item.(model.Service) 374 | } 375 | 376 | var hosts []model.Instance 377 | //select healthy instances 378 | for _, host := range dom.Hosts { 379 | if host.Healthy && host.Enable && host.Weight > 0 { 380 | for i := 0; i < int(math.Ceil(host.Weight)); i++ { 381 | hosts = append(hosts, host) 382 | } 383 | } 384 | } 385 | 386 | return hosts 387 | } 388 | 389 | func (vc *NacosClient) Contains(dom, clientIP string, host model.Instance) bool { 390 | hosts := vc.SrvInstances(dom, clientIP) 391 | 392 | for _, host1 := range hosts { 393 | if reflect.DeepEqual(host1, host) { 394 | return true 395 | } 396 | } 397 | 398 | return false 399 | } 400 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------