tools/ipam-autopilot/container/api.go (440 lines of code) (raw):

// Copyright 2021 Google LLC // // 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. package main import ( "context" "database/sql" "encoding/json" "fmt" "log" "net" "os" "strconv" "strings" "github.com/apparentlymart/go-cidr/cidr" "github.com/gofiber/fiber/v2" ) type CreateRoutingDomainRequest struct { Name string `json:"name"` Vpcs []string `json:"vpcs"` } type UpdateRoutingDomainRequest struct { Name JSONString `json:"name"` Vpcs JSONStringArray `json:"vpcs"` } type RangeRequest struct { Parent string `json:"parent"` Name string `json:"name"` Range_size int `json:"range_size"` Domain string `json:"domain"` Cidr string `json:"cidr"` } func GetRanges(c *fiber.Ctx) error { var results []*fiber.Map ranges, err := GetRangesFromDB() if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } for i := 0; i < len(ranges); i++ { results = append(results, &fiber.Map{ "id": ranges[i].Subnet_id, "parent": ranges[i].Parent_id, "name": ranges[i].Name, "cidr": ranges[i].Cidr, }) } return c.Status(200).JSON(results) } func GetRange(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } rang, err := GetRangeFromDB(id) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } return c.Status(200).JSON(&fiber.Map{ "id": rang.Subnet_id, "parent": rang.Parent_id, "name": rang.Name, "cidr": rang.Cidr, }) } func DeleteRange(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } err = DeleteRangeFromDb(id) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } return c.Status(200).JSON(&fiber.Map{ "success": true, }) } func CreateNewRange(c *fiber.Ctx) error { ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { log.Fatal(err) } // Instantiate new RangeRequest struct p := RangeRequest{} // Parse body into RangeRequest struct if err := c.BodyParser(&p); err != nil { fmt.Printf("Failed parsing body. %s Bad format %v", string(c.Body()), err) tx.Rollback() return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Bad format %v", err), }) } var routingDomain *RoutingDomain if p.Domain == "" { routingDomain, err = GetDefaultRoutingDomainFromDB(tx) if err != nil { fmt.Printf("Error %v", err) tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": "Couldn't retrieve default routing domain", }) } } else { domain_id, err := strconv.ParseInt(p.Domain, 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } routingDomain, err = GetRoutingDomainFromDB(domain_id) if err != nil { fmt.Printf("Error %v", err) tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": "Couldn't retrieve default routing domain", }) } } if p.Cidr != "" { return directInsert(c, tx, p, routingDomain) } else { return findNewLeaseAndInsert(c, tx, p, routingDomain) } } func directInsert(c *fiber.Ctx, tx *sql.Tx, p RangeRequest, routingDomain *RoutingDomain) error { var err error domain_id, err := strconv.ParseInt(p.Domain, 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Domain needs to be an integer %v", err), }) } parent_id := int64(-1) if p.Parent != "" { parent_id, err = strconv.ParseInt(p.Parent, 10, 64) if err != nil { rangeFromDb, err := getRangeByCidrAndRoutingDomain(tx, p.Parent, int(domain_id)) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Parent needs to be either a cidr range within the routing domain or the id of a valid range %v", err), }) } parent_id = int64(rangeFromDb.Subnet_id) } } id, err := CreateRangeInDb(tx, parent_id, int(domain_id), p.Name, p.Cidr) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new Subnet Lease %v", err), }) } err = tx.Commit() if err != nil { log.Fatal(err) } return c.Status(200).JSON(&fiber.Map{ "id": id, "cidr": p.Cidr, }) } func findNewLeaseAndInsert(c *fiber.Ctx, tx *sql.Tx, p RangeRequest, routingDomain *RoutingDomain) error { var err error var parent *Range if p.Parent != "" { parent_id, err := strconv.ParseInt(p.Parent, 10, 64) if err != nil { parent, err = getRangeByCidrAndRoutingDomain(tx, p.Parent, routingDomain.Id) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Parent needs to be either a cidr range within the routing domain or the id of a valid range %v", err), }) } } else { parent, err = GetRangeFromDBWithTx(tx, parent_id) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new Subnet Lease %v", err), }) } } } else { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": "Please provide the ID of a parent range", }) } range_size := p.Range_size subnet_ranges, err := GetRangesForParentFromDB(tx, int64(parent.Subnet_id)) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new Subnet Lease %v", err), }) } if os.Getenv("CAI_ORG_ID") != "" { log.Printf("CAI for org %s enabled", os.Getenv("CAI_ORG_ID")) // Integrating ranges from the VPC -- start vpcs := strings.Split(routingDomain.Vpcs, ",") log.Printf("Looking for subnets in vpcs %v", vpcs) ranges, err := GetRangesForNetwork(fmt.Sprintf("organizations/%s", os.Getenv("CAI_ORG_ID")), vpcs) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("error %v", err), }) } log.Printf("Found %d subnets in vpcs %v", len(ranges), vpcs) for j := 0; j < len(ranges); j++ { vpc_range := ranges[j] if !ContainsRange(subnet_ranges, vpc_range.cidr) { log.Printf("Adding range %s from CAI", vpc_range.cidr) subnet_ranges = append(subnet_ranges, Range{ Cidr: vpc_range.cidr, }) } for k := 0; k < len(vpc_range.secondaryRanges); k++ { secondaryRange := vpc_range.secondaryRanges[k] if !ContainsRange(subnet_ranges, secondaryRange.cidr) { log.Printf("Adding secondary range %s from CAI", vpc_range.cidr) subnet_ranges = append(subnet_ranges, Range{ Cidr: secondaryRange.cidr, }) } } } // Integrating ranges from the VPC -- end } else { log.Printf("Not checking CAI, env variable with Org ID not set") } subnet, subnetOnes, err := findNextSubnet(int(range_size), parent.Cidr, subnet_ranges) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new Subnet Lease %v", err), }) } nextSubnet, _ := cidr.NextSubnet(subnet, int(range_size)) log.Printf("next subnet will be starting with %s", nextSubnet.IP.String()) id, err := CreateRangeInDb(tx, int64(parent.Subnet_id), routingDomain.Id, p.Name, fmt.Sprintf("%s/%d", subnet.IP.To4().String(), subnetOnes)) if err != nil { tx.Rollback() return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new Subnet Lease %v", err), }) } err = tx.Commit() if err != nil { log.Fatal(err) } return c.Status(200).JSON(&fiber.Map{ "id": id, "cidr": fmt.Sprintf("%s/%d", subnet.IP.To4().String(), subnetOnes), }) } func findNextSubnet(range_size int, sourceRange string, existingRanges []Range) (*net.IPNet, int, error) { _, parentNet, err := net.ParseCIDR(sourceRange) if err != nil { return nil, -1, err } subnet, subnetOnes, err := createNewSubnetLease(sourceRange, range_size, 0) if err != nil { return nil, -1, err } log.Printf("new subnet lease %s/%d", subnet.IP.String(), subnetOnes) var lastSubnet = false for { err = verifyNoOverlap(sourceRange, existingRanges, subnet) if err == nil { break } else if !lastSubnet { subnet, lastSubnet = cidr.NextSubnet(subnet, int(range_size)) if !parentNet.Contains(subnet.IP) { return nil, -1, fmt.Errorf("no_address_range_available_in_parent") } } else { return nil, -1, err } } return subnet, subnetOnes, nil } func GetRoutingDomain(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } domain, err := GetRoutingDomainFromDB(id) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } return c.Status(200).JSON(&fiber.Map{ "id": domain.Id, "name": domain.Name, "vpcs": domain.Vpcs, }) } func DeleteRoutingDomain(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } err = DeleteRoutingDomainFromDB(id) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } return c.Status(200).JSON(&fiber.Map{}) } func GetRoutingDomains(c *fiber.Ctx) error { var results []*fiber.Map domains, err := GetRoutingDomainsFromDB() if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } for i := 0; i < len(domains); i++ { results = append(results, &fiber.Map{ "id": domains[i].Id, "name": domains[i].Name, "vpcs": domains[i].Vpcs, }) } return c.Status(200).JSON(results) } func UpdateRoutingDomain(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("%v", err), }) } // Instantiate new UpdateRoutingDomainRequest struct p := new(UpdateRoutingDomainRequest) // Parse body into UpdateRoutingDomainRequest struct if err := c.BodyParser(p); err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Bad format %v", err), }) } err = UpdateRoutingDomainOnDb(id, p.Name, p.Vpcs) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to update routing domain %v", err), }) } return c.Status(200).JSON(&fiber.Map{}) } func CreateRoutingDomain(c *fiber.Ctx) error { // Instantiate new UpdateRoutingDomainRequest struct p := new(CreateRoutingDomainRequest) // Parse body into UpdateRoutingDomainRequest struct if err := c.BodyParser(p); err != nil { return c.Status(400).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Bad format %v", err), }) } id, err := CreateRoutingDomainOnDb(p.Name, p.Vpcs) if err != nil { return c.Status(503).JSON(&fiber.Map{ "success": false, "message": fmt.Sprintf("Unable to create new routing domain %v", err), }) } return c.Status(200).JSON(&fiber.Map{ "id": id, }) } func ContainsRange(array []Range, cidr string) bool { for i := 0; i < len(array); i++ { if cidr == array[i].Cidr { return true } } return false } type JSONString struct { Value string Set bool } func (i *JSONString) UnmarshalJSON(data []byte) error { i.Set = true var val string if err := json.Unmarshal(data, &val); err != nil { return err } i.Value = val return nil } type JSONStringArray struct { Value []string Set bool } func (i *JSONStringArray) UnmarshalJSON(data []byte) error { i.Set = true var val []string if err := json.Unmarshal(data, &val); err != nil { return err } i.Value = val return nil }