|
@@ -0,0 +1,265 @@
|
|
|
+// Copyright 2015 flannel authors
|
|
|
+//
|
|
|
+// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+// you may not use this file except in compliance with the License.
|
|
|
+// You may obtain a copy of the License at
|
|
|
+//
|
|
|
+// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+//
|
|
|
+// Unless required by applicable law or agreed to in writing, software
|
|
|
+// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+// See the License for the specific language governing permissions and
|
|
|
+// limitations under the License.
|
|
|
+
|
|
|
+// This work borrows from the https://github.com/kelseyhightower/flannel-route-manager
|
|
|
+// project which has the following license agreement.
|
|
|
+
|
|
|
+// Copyright (c) 2014 Kelsey Hightower
|
|
|
+
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
+// this software and associated documentation files (the "Software"), to deal in
|
|
|
+// the Software without restriction, including without limitation the rights to
|
|
|
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
|
+// of the Software, and to permit persons to whom the Software is furnished to do
|
|
|
+// so, subject to the following conditions:
|
|
|
+
|
|
|
+// The above copyright notice and this permission notice shall be included in all
|
|
|
+// copies or substantial portions of the Software.
|
|
|
+
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
+// SOFTWARE.
|
|
|
+
|
|
|
+package gce
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "net"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/coreos/flannel/Godeps/_workspace/src/code.google.com/p/goauth2/compute/serviceaccount"
|
|
|
+ "github.com/coreos/flannel/Godeps/_workspace/src/code.google.com/p/google-api-go-client/compute/v1"
|
|
|
+ "github.com/coreos/flannel/Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi"
|
|
|
+ log "github.com/coreos/flannel/Godeps/_workspace/src/github.com/golang/glog"
|
|
|
+ "github.com/coreos/flannel/Godeps/_workspace/src/golang.org/x/net/context"
|
|
|
+
|
|
|
+ "github.com/coreos/flannel/backend"
|
|
|
+ "github.com/coreos/flannel/pkg/ip"
|
|
|
+ "github.com/coreos/flannel/subnet"
|
|
|
+)
|
|
|
+
|
|
|
+var metadataEndpoint = "http://169.254.169.254/computeMetadata/v1"
|
|
|
+
|
|
|
+var replacer = strings.NewReplacer(".", "-", "/", "-")
|
|
|
+
|
|
|
+type GCEBackend struct {
|
|
|
+ network string
|
|
|
+ project string
|
|
|
+ sm subnet.Manager
|
|
|
+ config *subnet.Config
|
|
|
+ lease *subnet.Lease
|
|
|
+ ctx context.Context
|
|
|
+ cancel context.CancelFunc
|
|
|
+ wg sync.WaitGroup
|
|
|
+ computeService *compute.Service
|
|
|
+ gceNetwork *compute.Network
|
|
|
+ gceInstance *compute.Instance
|
|
|
+}
|
|
|
+
|
|
|
+func New(sm subnet.Manager, network string, config *subnet.Config) backend.Backend {
|
|
|
+ ctx, cancel := context.WithCancel(context.Background())
|
|
|
+
|
|
|
+ gb := GCEBackend{
|
|
|
+ sm: sm,
|
|
|
+ config: config,
|
|
|
+ ctx: ctx,
|
|
|
+ cancel: cancel,
|
|
|
+ network: network,
|
|
|
+ }
|
|
|
+ return &gb
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) Init(extIface *net.Interface, extIP net.IP) (*backend.SubnetDef, error) {
|
|
|
+ attrs := subnet.LeaseAttrs{
|
|
|
+ PublicIP: ip.FromIP(extIP),
|
|
|
+ }
|
|
|
+
|
|
|
+ l, err := g.sm.AcquireLease(g.ctx, g.network, &attrs)
|
|
|
+ switch err {
|
|
|
+ case nil:
|
|
|
+ g.lease = l
|
|
|
+
|
|
|
+ case context.Canceled, context.DeadlineExceeded:
|
|
|
+ return nil, err
|
|
|
+
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("failed to acquire lease: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ client, err := serviceaccount.NewClient(&serviceaccount.Options{})
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error creating client: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ g.computeService, err = compute.New(client)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error creating compute service: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ networkName, err := networkFromMetadata()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting network metadata: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ g.project, err = projectFromMetadata()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting project: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ instanceName, err := instanceNameFromMetadata()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting instance name: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ instanceZone, err := instanceZoneFromMetadata()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting instance zone: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ g.gceNetwork, err = g.computeService.Networks.Get(g.project, networkName).Do()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting network from compute service: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ g.gceInstance, err = g.computeService.Instances.Get(g.project, instanceZone, instanceName).Do()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error getting instance from compute service: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ found, err := g.handleMatchingRoute(l.Subnet.String())
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error handling matching route: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if !found {
|
|
|
+ operation, err := g.insertRoute(l.Subnet.String())
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("error inserting route: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = g.pollOperationStatus(operation.Name)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("insert operaiton failed: ", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return &backend.SubnetDef{
|
|
|
+ Net: l.Subnet,
|
|
|
+ MTU: extIface.MTU,
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) Run() {
|
|
|
+ log.Info("GCE backend running")
|
|
|
+ subnet.LeaseRenewer(g.ctx, g.sm, g.network, g.lease)
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) Stop() {
|
|
|
+ log.Info("GCE backend stopping")
|
|
|
+ g.cancel()
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) Name() string {
|
|
|
+ return "gce"
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) pollOperationStatus(operationName string) error {
|
|
|
+ for i := 0; i < 100; i++ {
|
|
|
+ operation, err := g.computeService.GlobalOperations.Get(g.project, operationName).Do()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("error fetching operation status: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if operation.Error != nil {
|
|
|
+ return fmt.Errorf("error running operation: %v", operation.Error)
|
|
|
+ }
|
|
|
+
|
|
|
+ if i%5 == 0 {
|
|
|
+ log.Infof("%v operation status: %v waiting for completion...", operation.OperationType, operation.Status)
|
|
|
+ }
|
|
|
+
|
|
|
+ if operation.Status == "DONE" {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ time.Sleep(time.Second)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("timeout waiting for operation to finish")
|
|
|
+}
|
|
|
+
|
|
|
+//returns true if an exact matching rule is found
|
|
|
+func (g *GCEBackend) handleMatchingRoute(subnet string) (bool, error) {
|
|
|
+ matchingRoute, err := g.getRoute(subnet)
|
|
|
+ if err != nil {
|
|
|
+ if apiError, ok := err.(*googleapi.Error); ok {
|
|
|
+ if apiError.Code != 404 {
|
|
|
+ return false, fmt.Errorf("error getting the route err: %v", err)
|
|
|
+ }
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ return false, fmt.Errorf("error getting googleapi: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if matchingRoute.NextHopInstance == g.gceInstance.SelfLink {
|
|
|
+ log.Info("Exact pre-existing route found")
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ log.Info("Deleting conflicting route")
|
|
|
+ operation, err := g.deleteRoute(subnet)
|
|
|
+ if err != nil {
|
|
|
+ return false, fmt.Errorf("error deleting conflicting route : %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = g.pollOperationStatus(operation.Name)
|
|
|
+ if err != nil {
|
|
|
+ return false, fmt.Errorf("delete operation failed: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return false, nil
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) getRoute(subnet string) (*compute.Route, error) {
|
|
|
+ routeName := formatRouteName(g.gceNetwork.Name, subnet)
|
|
|
+ return g.computeService.Routes.Get(g.project, routeName).Do()
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) deleteRoute(subnet string) (*compute.Operation, error) {
|
|
|
+ routeName := formatRouteName(g.gceNetwork.Name, subnet)
|
|
|
+ return g.computeService.Routes.Delete(g.project, routeName).Do()
|
|
|
+}
|
|
|
+
|
|
|
+func (g *GCEBackend) insertRoute(subnet string) (*compute.Operation, error) {
|
|
|
+ log.Infof("Inserting route for subnet: %v", subnet)
|
|
|
+ route := &compute.Route{
|
|
|
+ Name: formatRouteName(g.gceNetwork.Name, subnet),
|
|
|
+ DestRange: subnet,
|
|
|
+ Network: g.gceNetwork.SelfLink,
|
|
|
+ NextHopInstance: g.gceInstance.SelfLink,
|
|
|
+ Priority: 1000,
|
|
|
+ Tags: []string{},
|
|
|
+ }
|
|
|
+ return g.computeService.Routes.Insert(g.project, route).Do()
|
|
|
+}
|
|
|
+
|
|
|
+func formatRouteName(network, subnet string) string {
|
|
|
+ return fmt.Sprintf("flannel-%s-%s", network, replacer.Replace(subnet))
|
|
|
+}
|