openstack_routes.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  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. package openstack
  14. import (
  15. "context"
  16. "errors"
  17. "net"
  18. "github.com/gophercloud/gophercloud"
  19. "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
  20. "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
  21. neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
  22. "k8s.io/apimachinery/pkg/types"
  23. cloudprovider "k8s.io/cloud-provider"
  24. "k8s.io/klog"
  25. )
  26. var errNoRouterID = errors.New("router-id not set in cloud provider config")
  27. var _ cloudprovider.Routes = (*Routes)(nil)
  28. // Routes implements the cloudprovider.Routes for OpenStack clouds
  29. type Routes struct {
  30. compute *gophercloud.ServiceClient
  31. network *gophercloud.ServiceClient
  32. opts RouterOpts
  33. }
  34. // NewRoutes creates a new instance of Routes
  35. func NewRoutes(compute *gophercloud.ServiceClient, network *gophercloud.ServiceClient, opts RouterOpts) (cloudprovider.Routes, error) {
  36. if opts.RouterID == "" {
  37. return nil, errNoRouterID
  38. }
  39. return &Routes{
  40. compute: compute,
  41. network: network,
  42. opts: opts,
  43. }, nil
  44. }
  45. // ListRoutes lists all managed routes that belong to the specified clusterName
  46. func (r *Routes) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) {
  47. klog.V(4).Infof("ListRoutes(%v)", clusterName)
  48. nodeNamesByAddr := make(map[string]types.NodeName)
  49. err := foreachServer(r.compute, servers.ListOpts{}, func(srv *servers.Server) (bool, error) {
  50. addrs, err := nodeAddresses(srv)
  51. if err != nil {
  52. return false, err
  53. }
  54. name := mapServerToNodeName(srv)
  55. for _, addr := range addrs {
  56. nodeNamesByAddr[addr.Address] = name
  57. }
  58. return true, nil
  59. })
  60. if err != nil {
  61. return nil, err
  62. }
  63. router, err := routers.Get(r.network, r.opts.RouterID).Extract()
  64. if err != nil {
  65. return nil, err
  66. }
  67. var routes []*cloudprovider.Route
  68. for _, item := range router.Routes {
  69. nodeName, foundNode := nodeNamesByAddr[item.NextHop]
  70. if !foundNode {
  71. nodeName = types.NodeName(item.NextHop)
  72. }
  73. route := cloudprovider.Route{
  74. Name: item.DestinationCIDR,
  75. TargetNode: nodeName, //contains the nexthop address if node was not found
  76. Blackhole: !foundNode,
  77. DestinationCIDR: item.DestinationCIDR,
  78. }
  79. routes = append(routes, &route)
  80. }
  81. return routes, nil
  82. }
  83. func updateRoutes(network *gophercloud.ServiceClient, router *routers.Router, newRoutes []routers.Route) (func(), error) {
  84. origRoutes := router.Routes // shallow copy
  85. _, err := routers.Update(network, router.ID, routers.UpdateOpts{
  86. Routes: newRoutes,
  87. }).Extract()
  88. if err != nil {
  89. return nil, err
  90. }
  91. unwinder := func() {
  92. klog.V(4).Infof("Reverting routes change to router %v", router.ID)
  93. _, err := routers.Update(network, router.ID, routers.UpdateOpts{
  94. Routes: origRoutes,
  95. }).Extract()
  96. if err != nil {
  97. klog.Warningf("Unable to reset routes during error unwind: %v", err)
  98. }
  99. }
  100. return unwinder, nil
  101. }
  102. func updateAllowedAddressPairs(network *gophercloud.ServiceClient, port *neutronports.Port, newPairs []neutronports.AddressPair) (func(), error) {
  103. origPairs := port.AllowedAddressPairs // shallow copy
  104. _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{
  105. AllowedAddressPairs: &newPairs,
  106. }).Extract()
  107. if err != nil {
  108. return nil, err
  109. }
  110. unwinder := func() {
  111. klog.V(4).Infof("Reverting allowed-address-pairs change to port %v", port.ID)
  112. _, err := neutronports.Update(network, port.ID, neutronports.UpdateOpts{
  113. AllowedAddressPairs: &origPairs,
  114. }).Extract()
  115. if err != nil {
  116. klog.Warningf("Unable to reset allowed-address-pairs during error unwind: %v", err)
  117. }
  118. }
  119. return unwinder, nil
  120. }
  121. // CreateRoute creates the described managed route
  122. func (r *Routes) CreateRoute(ctx context.Context, clusterName string, nameHint string, route *cloudprovider.Route) error {
  123. klog.V(4).Infof("CreateRoute(%v, %v, %v)", clusterName, nameHint, route)
  124. onFailure := newCaller()
  125. ip, _, _ := net.ParseCIDR(route.DestinationCIDR)
  126. isCIDRv6 := ip.To4() == nil
  127. addr, err := getAddressByName(r.compute, route.TargetNode, isCIDRv6)
  128. if err != nil {
  129. return err
  130. }
  131. klog.V(4).Infof("Using nexthop %v for node %v", addr, route.TargetNode)
  132. router, err := routers.Get(r.network, r.opts.RouterID).Extract()
  133. if err != nil {
  134. return err
  135. }
  136. routes := router.Routes
  137. for _, item := range routes {
  138. if item.DestinationCIDR == route.DestinationCIDR && item.NextHop == addr {
  139. klog.V(4).Infof("Skipping existing route: %v", route)
  140. return nil
  141. }
  142. }
  143. routes = append(routes, routers.Route{
  144. DestinationCIDR: route.DestinationCIDR,
  145. NextHop: addr,
  146. })
  147. unwind, err := updateRoutes(r.network, router, routes)
  148. if err != nil {
  149. return err
  150. }
  151. defer onFailure.call(unwind)
  152. // get the port of addr on target node.
  153. portID, err := getPortIDByIP(r.compute, route.TargetNode, addr)
  154. if err != nil {
  155. return err
  156. }
  157. port, err := getPortByID(r.network, portID)
  158. if err != nil {
  159. return err
  160. }
  161. found := false
  162. for _, item := range port.AllowedAddressPairs {
  163. if item.IPAddress == route.DestinationCIDR {
  164. klog.V(4).Infof("Found existing allowed-address-pair: %v", item)
  165. found = true
  166. break
  167. }
  168. }
  169. if !found {
  170. newPairs := append(port.AllowedAddressPairs, neutronports.AddressPair{
  171. IPAddress: route.DestinationCIDR,
  172. })
  173. unwind, err := updateAllowedAddressPairs(r.network, port, newPairs)
  174. if err != nil {
  175. return err
  176. }
  177. defer onFailure.call(unwind)
  178. }
  179. klog.V(4).Infof("Route created: %v", route)
  180. onFailure.disarm()
  181. return nil
  182. }
  183. // DeleteRoute deletes the specified managed route
  184. func (r *Routes) DeleteRoute(ctx context.Context, clusterName string, route *cloudprovider.Route) error {
  185. klog.V(4).Infof("DeleteRoute(%v, %v)", clusterName, route)
  186. onFailure := newCaller()
  187. ip, _, _ := net.ParseCIDR(route.DestinationCIDR)
  188. isCIDRv6 := ip.To4() == nil
  189. var addr string
  190. // Blackhole routes are orphaned and have no counterpart in OpenStack
  191. if !route.Blackhole {
  192. var err error
  193. addr, err = getAddressByName(r.compute, route.TargetNode, isCIDRv6)
  194. if err != nil {
  195. return err
  196. }
  197. }
  198. router, err := routers.Get(r.network, r.opts.RouterID).Extract()
  199. if err != nil {
  200. return err
  201. }
  202. routes := router.Routes
  203. index := -1
  204. for i, item := range routes {
  205. if item.DestinationCIDR == route.DestinationCIDR && (item.NextHop == addr || route.Blackhole && item.NextHop == string(route.TargetNode)) {
  206. index = i
  207. break
  208. }
  209. }
  210. if index == -1 {
  211. klog.V(4).Infof("Skipping non-existent route: %v", route)
  212. return nil
  213. }
  214. // Delete element `index`
  215. routes[index] = routes[len(routes)-1]
  216. routes = routes[:len(routes)-1]
  217. unwind, err := updateRoutes(r.network, router, routes)
  218. // If this was a blackhole route we are done, there are no ports to update
  219. if err != nil || route.Blackhole {
  220. return err
  221. }
  222. defer onFailure.call(unwind)
  223. // get the port of addr on target node.
  224. portID, err := getPortIDByIP(r.compute, route.TargetNode, addr)
  225. if err != nil {
  226. return err
  227. }
  228. port, err := getPortByID(r.network, portID)
  229. if err != nil {
  230. return err
  231. }
  232. addrPairs := port.AllowedAddressPairs
  233. index = -1
  234. for i, item := range addrPairs {
  235. if item.IPAddress == route.DestinationCIDR {
  236. index = i
  237. break
  238. }
  239. }
  240. if index != -1 {
  241. // Delete element `index`
  242. addrPairs[index] = addrPairs[len(addrPairs)-1]
  243. addrPairs = addrPairs[:len(addrPairs)-1]
  244. unwind, err := updateAllowedAddressPairs(r.network, port, addrPairs)
  245. if err != nil {
  246. return err
  247. }
  248. defer onFailure.call(unwind)
  249. }
  250. klog.V(4).Infof("Route deleted: %v", route)
  251. onFailure.disarm()
  252. return nil
  253. }
  254. func getPortIDByIP(compute *gophercloud.ServiceClient, targetNode types.NodeName, ipAddress string) (string, error) {
  255. srv, err := getServerByName(compute, targetNode)
  256. if err != nil {
  257. return "", err
  258. }
  259. interfaces, err := getAttachedInterfacesByID(compute, srv.ID)
  260. if err != nil {
  261. return "", err
  262. }
  263. for _, intf := range interfaces {
  264. for _, fixedIP := range intf.FixedIPs {
  265. if fixedIP.IPAddress == ipAddress {
  266. return intf.PortID, nil
  267. }
  268. }
  269. }
  270. return "", ErrNotFound
  271. }
  272. func getPortByID(client *gophercloud.ServiceClient, portID string) (*neutronports.Port, error) {
  273. targetPort, err := neutronports.Get(client, portID).Extract()
  274. if err != nil {
  275. return nil, err
  276. }
  277. if targetPort == nil {
  278. return nil, ErrNotFound
  279. }
  280. return targetPort, nil
  281. }