metadatahandler.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Copyright 2019 Yunion
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package metadata
  15. // NOTE keep imports minimal. DO NOT IMPORT guestman
  16. import (
  17. "context"
  18. "encoding/base64"
  19. "fmt"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "strconv"
  24. "strings"
  25. "yunion.io/x/log"
  26. "yunion.io/x/pkg/errors"
  27. "yunion.io/x/pkg/util/netutils"
  28. identity_api "yunion.io/x/onecloud/pkg/apis/identity"
  29. "yunion.io/x/onecloud/pkg/appsrv"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
  31. "yunion.io/x/onecloud/pkg/hostman/guestman/desc"
  32. "yunion.io/x/onecloud/pkg/hostman/hostutils"
  33. "yunion.io/x/onecloud/pkg/hostman/options"
  34. "yunion.io/x/onecloud/pkg/httperrors"
  35. "yunion.io/x/onecloud/pkg/mcclient/auth"
  36. "yunion.io/x/onecloud/pkg/proxy"
  37. )
  38. func Start(app *appsrv.Application, s *Service) {
  39. s.addHandler(app)
  40. addr := net.JoinHostPort(s.Address, strconv.Itoa(s.Port))
  41. log.Infof("Start metadata service on http://%s", addr)
  42. app.ListenAndServeWithoutCleanup(addr, "", "")
  43. }
  44. type DescGetter interface {
  45. Get(ip string) (guestDesc *desc.SGuestDesc)
  46. }
  47. type DescGetterFunc func(ip string) (guestDesc *desc.SGuestDesc)
  48. func (f DescGetterFunc) Get(ip string) (guestDesc *desc.SGuestDesc) {
  49. return f(ip)
  50. }
  51. type Service struct {
  52. Address string
  53. Port int
  54. DescGetter DescGetter
  55. }
  56. func (s *Service) getGuestDesc(r *http.Request) (guestDesc *desc.SGuestDesc) {
  57. ip, _, err := net.SplitHostPort(r.RemoteAddr)
  58. if err != nil {
  59. panic(errors.Wrapf(err, "SplitHostPort %s", r.RemoteAddr))
  60. }
  61. guestDesc = s.DescGetter.Get(ip)
  62. return
  63. }
  64. func (s *Service) monitorPrefix() string {
  65. return "/monitor"
  66. }
  67. func (s *Service) addHandler(app *appsrv.Application) {
  68. prefix := ""
  69. for _, method := range []string{"GET", "HEAD"} {
  70. app.AddHandler(method, fmt.Sprintf("%s/<version:%s>",
  71. prefix, `(latest|\d{4}-\d{2}-\d{2})`), s.versionOnly)
  72. }
  73. for _, method := range []string{"GET", "HEAD"} {
  74. app.AddHandler(method, fmt.Sprintf("%s/<version:%s>/user-data",
  75. prefix, `(latest|\d{4}-\d{2}-\d{2})`), s.userData)
  76. app.AddHandler(method, fmt.Sprintf("%s/<version:%s>/meta-data",
  77. prefix, `(latest|\d{4}-\d{2}-\d{2})`), s.metaData)
  78. }
  79. app.AddReverseProxyHandler(s.monitorPrefix(), s.monitorReverseEndpoint(), s.requestManipulator)
  80. }
  81. func (s *Service) versionOnly(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  82. hostutils.Response(ctx, w, strings.Join([]string{"meta-data", "user-data"}, "\n"))
  83. }
  84. func (s *Service) userData(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  85. guestDesc := s.getGuestDesc(r)
  86. if guestDesc == nil {
  87. hostutils.Response(ctx, w, "")
  88. return
  89. }
  90. guestUserData := guestDesc.UserData
  91. if guestUserData == "" {
  92. hostutils.Response(ctx, w, "")
  93. return
  94. }
  95. userDataDecoded, err := base64.StdEncoding.DecodeString(guestUserData)
  96. if err != nil {
  97. log.Errorf("Error format user_data %s, %s", guestDesc.Uuid, guestUserData)
  98. hostutils.Response(ctx, w, "")
  99. return
  100. }
  101. hostutils.Response(ctx, w, string(userDataDecoded))
  102. }
  103. func (s *Service) metaData(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  104. guestDesc := s.getGuestDesc(r)
  105. if guestDesc == nil {
  106. hostutils.Response(ctx, w, "")
  107. return
  108. }
  109. req := appsrv.SplitPath(r.URL.Path)[2:]
  110. if len(req) == 0 {
  111. resNames := []string{
  112. "ami-launch-index",
  113. "block-device-mapping/", "hostname",
  114. "instance-id", "instance-type",
  115. "local-hostname",
  116. "local-ipv4",
  117. "local-ipv6",
  118. "mac",
  119. "public-hostname", "public-ipv4",
  120. "network_config/",
  121. "local-sub-ipv4s",
  122. //"amiid", "ami-manifest-path",
  123. //"instance-action", "kernel-id",
  124. //"ipv4-associations", "network/",
  125. //"placement/", "public-keys/",
  126. //"reservation-id", "security-groups", "password",
  127. }
  128. if guestDesc.Pubkey != "" {
  129. resNames = append(resNames, "public-keys/")
  130. }
  131. if guestDesc.Zone != "" {
  132. resNames = append(resNames, "placement/")
  133. }
  134. if guestDesc.Secgroup != "" {
  135. resNames = append(resNames, "security-groups/")
  136. }
  137. hostutils.Response(ctx, w, strings.Join(resNames, "\n"))
  138. return
  139. } else {
  140. resName := req[0]
  141. switch resName {
  142. case "public-keys":
  143. if guestDesc.Pubkey != "" {
  144. if len(req) == 1 {
  145. hostutils.Response(ctx, w, "0=my-public-key")
  146. return
  147. } else if len(req) == 2 {
  148. hostutils.Response(ctx, w, "openssh-key")
  149. return
  150. } else if len(req) == 3 {
  151. pubkey := guestDesc.Pubkey
  152. hostutils.Response(ctx, w, pubkey)
  153. return
  154. }
  155. }
  156. case "hostname":
  157. hostutils.Response(ctx, w, guestDesc.Name)
  158. return
  159. case "public-hostname", "local-hostname":
  160. hostutils.Response(ctx, w, guestDesc.Hostname)
  161. return
  162. case "instance-id":
  163. hostutils.Response(ctx, w, guestDesc.Uuid)
  164. return
  165. case "instance-type":
  166. flavor := guestDesc.Flavor
  167. if flavor == "" {
  168. flavor = "customized"
  169. }
  170. hostutils.Response(ctx, w, flavor)
  171. return
  172. case "mac":
  173. macs := make([]string, 0)
  174. guestNics := guestDesc.Nics
  175. for _, nic := range guestNics {
  176. macs = append(macs, nic.Mac)
  177. }
  178. hostutils.Response(ctx, w, strings.Join(macs, "\n"))
  179. return
  180. case "local-ipv4":
  181. ips := make([]string, 0)
  182. guestNics := guestDesc.Nics
  183. for _, nic := range guestNics {
  184. ips = append(ips, nic.Ip)
  185. }
  186. hostutils.Response(ctx, w, strings.Join(ips, "\n"))
  187. return
  188. case "local-ipv6":
  189. ips := make([]string, 0)
  190. guestNics := guestDesc.Nics
  191. for _, nic := range guestNics {
  192. if len(nic.Ip6) > 0 {
  193. ips = append(ips, nic.Ip6)
  194. }
  195. }
  196. hostutils.Response(ctx, w, strings.Join(ips, "\n"))
  197. return
  198. case "local-sub-ipv4s":
  199. ips := make([]string, 0)
  200. guestNics := guestDesc.Nics
  201. for _, nic := range guestNics {
  202. if nic.Networkaddresses == nil {
  203. continue
  204. }
  205. nas, _ := nic.Networkaddresses.GetArray()
  206. for _, na := range nas {
  207. if typ, _ := na.GetString("type"); typ == "sub_ip" {
  208. ip, _ := na.GetString("ip_addr")
  209. if ip != "" {
  210. ips = append(ips, ip)
  211. }
  212. }
  213. }
  214. }
  215. hostutils.Response(ctx, w, strings.Join(ips, "\n"))
  216. return
  217. case "public-ipv4":
  218. ips := make([]string, 0)
  219. guestNics := guestDesc.Nics
  220. for _, nic := range guestNics {
  221. ipv4, _ := netutils.NewIPV4Addr(nic.Ip)
  222. if !netutils.IsPrivate(ipv4) {
  223. ips = append(ips, nic.Ip)
  224. }
  225. }
  226. hostutils.Response(ctx, w, strings.Join(ips, "\n"))
  227. return
  228. case "placement":
  229. if guestDesc.Zone != "" {
  230. if len(req) == 1 {
  231. hostutils.Response(ctx, w, "availability-zone")
  232. return
  233. } else if len(req) == 2 && req[1] == "availability-zone" {
  234. hostutils.Response(ctx, w, guestDesc.Zone)
  235. return
  236. }
  237. }
  238. case "security-groups":
  239. if guestDesc.Secgroup != "" {
  240. hostutils.Response(ctx, w, guestDesc.Secgroup)
  241. return
  242. }
  243. case "ami-launch-index":
  244. hostutils.Response(ctx, w, "0")
  245. return
  246. case "network_config":
  247. if len(req) == 1 {
  248. hostutils.Response(ctx, w,
  249. strings.Join([]string{"name", "content_path"}, "\n"))
  250. return
  251. } else if len(req) == 2 {
  252. if req[1] == "name" {
  253. hostutils.Response(ctx, w, "network_config")
  254. return
  255. } else if req[1] == "content_path" {
  256. hostutils.Response(ctx, w, "content/0001")
  257. return
  258. }
  259. }
  260. case "block-device-mapping":
  261. guestDisks := guestDesc.Disks
  262. swapDisks := make([]string, 0)
  263. dataDisk := make([]string, 0)
  264. for _, d := range guestDisks {
  265. if d.Fs == "swap" {
  266. swapDisks = append(swapDisks, strconv.Itoa(int(d.Index)))
  267. } else {
  268. dataDisk = append(dataDisk, strconv.Itoa(int(d.Index)))
  269. }
  270. }
  271. if len(req) == 1 {
  272. devs := []string{"root"}
  273. if len(swapDisks) > 0 {
  274. devs = append(devs, "swap")
  275. }
  276. if len(dataDisk) > 0 {
  277. for i := 0; i < len(dataDisk); i++ {
  278. devs = append(devs, fmt.Sprintf("ephemeral%d", i+1))
  279. }
  280. }
  281. hostutils.Response(ctx, w, strings.Join(devs, "\n"))
  282. return
  283. } else if len(req) == 2 {
  284. devs := []string{}
  285. if req[1] == "root" {
  286. devs = append(devs, "/dev/root")
  287. } else if req[1] == "swap" {
  288. for i := 0; i < len(swapDisks); i++ {
  289. idx, _ := strconv.Atoi(swapDisks[i])
  290. devs = append(devs, fmt.Sprintf("/dev/vd%c1", 'a'+idx))
  291. }
  292. } else if strings.HasPrefix(req[1], "ephemeral") || strings.HasPrefix(req[1], "ebs") {
  293. for i := 0; i < len(dataDisk); i++ {
  294. idx, _ := strconv.Atoi(dataDisk[i])
  295. devs = append(devs, fmt.Sprintf("/dev/vd%c1", 'a'+idx))
  296. }
  297. }
  298. hostutils.Response(ctx, w, strings.Join(devs, "\n"))
  299. return
  300. }
  301. }
  302. }
  303. hostutils.Response(ctx, w, httperrors.NewNotFoundError("Resource not handled"))
  304. }
  305. func (s *Service) monitorReverseEndpoint() *proxy.SEndpointFactory {
  306. f := func(ctx context.Context, r *http.Request) (string, error) {
  307. guestDesc := s.getGuestDesc(r)
  308. if guestDesc == nil {
  309. return "", httperrors.NewNotFoundError("vm not found")
  310. }
  311. s := auth.GetAdminSession(ctx, options.HostOptions.Region)
  312. srcURL, err := tsdb.GetDefaultServiceSourceURL(s, identity_api.EndpointInterfaceInternal)
  313. if err != nil {
  314. return "", errors.Wrap(err, "monitorReverseEndpoint get tsdb url")
  315. }
  316. return srcURL, nil
  317. }
  318. return proxy.NewEndpointFactory(f, "monitorService")
  319. }
  320. func (s *Service) requestManipulator(ctx context.Context, r *http.Request) (*http.Request, error) {
  321. if err := s.rewriteTelegrafInfluxBodyIfNeeded(ctx, r); err != nil {
  322. log.Errorf("failed rewrite telegraf body %s", err)
  323. }
  324. path := r.URL.Path[len(s.monitorPrefix()):]
  325. log.Debugf("Path: %s => %s", r.URL.Path, path)
  326. r.URL = &url.URL{
  327. Path: path,
  328. RawQuery: r.URL.RawQuery,
  329. Fragment: r.URL.Fragment,
  330. }
  331. return r, nil
  332. }