subnetwork.go (124 lines of code) (raw):

// Copyright 2017 Google Inc. All Rights Reserved. // // 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 daisy import ( "context" "encoding/json" "fmt" "net" "net/http" "regexp" daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) var ( subnetworkURLRegex = regexp.MustCompile(fmt.Sprintf(`^(projects/(?P<project>%[1]s)/)?regions/(?P<region>%[2]s)/subnetworks/(?P<subnetwork>%[2]s)$`, projectRgxStr, rfc1035)) ) func (w *Workflow) subnetworkExists(project, region, subnetwork string) (bool, DError) { return w.subnetworkCache.resourceExists(func(project, region string, opts ...daisyCompute.ListCallOption) (interface{}, error) { return w.ComputeClient.ListSubnetworks(project, region) }, project, region, subnetwork) } // Subnetwork is used to create a GCE subnetwork. type Subnetwork struct { compute.Subnetwork Resource } // MarshalJSON is a hacky workaround to compute.Subnetwork's implementation. func (sn *Subnetwork) MarshalJSON() ([]byte, error) { return json.Marshal(*sn) } func (sn *Subnetwork) populate(ctx context.Context, s *Step) DError { var errs DError sn.Name, errs = sn.Resource.populateWithGlobal(ctx, s, sn.Name) sn.Description = strOr(sn.Description, defaultDescription("Subnetwork", s.w.Name, s.w.username)) r := sn.Region if r == "" { r = getRegionFromZone(s.w.Zone) } sn.link = fmt.Sprintf("projects/%s/regions/%s/subnetworks/%s", sn.Project, r, sn.Name) return errs } func (sn *Subnetwork) validate(ctx context.Context, s *Step) DError { pre := fmt.Sprintf("cannot create subnetwork %q", sn.daisyName) errs := sn.Resource.validate(ctx, s, pre) if sn.Name == "" { errs = addErrs(errs, Errf("%s: name is mandatory", pre)) } if sn.Network == "" { errs = addErrs(errs, Errf("%s: network is mandatory", pre)) } sn.Region = strOr(sn.Region, getRegionFromZone(s.w.Zone)) if _, _, err := net.ParseCIDR(sn.IpCidrRange); err != nil { errs = addErrs(errs, Errf("%s: bad IpCidrRange: %q, error: %v", pre, sn.IpCidrRange, err)) } // Register creation. errs = addErrs(errs, s.w.subnetworks.regCreate(sn.daisyName, &sn.Resource, s, false)) return errs } type subnetworkConnection struct { connector, disconnector *Step } type subnetworkRegistry struct { baseResourceRegistry connections map[string]map[string]*subnetworkConnection testDisconnectHelper func(nName, iName string, s *Step) DError } func newSubnetworkRegistry(w *Workflow) *subnetworkRegistry { nr := &subnetworkRegistry{baseResourceRegistry: baseResourceRegistry{w: w, typeName: "subnetwork", urlRgx: subnetworkURLRegex}} nr.baseResourceRegistry.deleteFn = nr.deleteFn nr.connections = map[string]map[string]*subnetworkConnection{} nr.init() return nr } func (nr *subnetworkRegistry) deleteFn(res *Resource) DError { m := NamedSubexp(subnetworkURLRegex, res.link) err := nr.w.ComputeClient.DeleteSubnetwork(m["project"], m["region"], m["subnetwork"]) if gErr, ok := err.(*googleapi.Error); ok && gErr.Code == http.StatusNotFound { return typedErr(resourceDNEError, "failed to delete subnetwork", err) } return newErr("failed to delete subnetwork", err) } func (nr *subnetworkRegistry) disconnectHelper(nName, iName string, s *Step) DError { if nr.testDisconnectHelper != nil { return nr.testDisconnectHelper(nName, iName, s) } pre := fmt.Sprintf("step %q cannot disconnect instance %q from subnetwork %q", s.name, iName, nName) var conn *subnetworkConnection if im, _ := nr.connections[nName]; im == nil { return Errf("%s: not connected", pre) } else if conn, _ = im[iName]; conn == nil { return Errf("%s: not attached", pre) } else if conn.disconnector != nil { return Errf("%s: already disconnected or concurrently disconnected by step %q", pre, conn.disconnector.name) } else if !s.nestedDepends(conn.connector) { return Errf("%s: step %q does not depend on connecting step %q", pre, s.name, conn.connector.name) } conn.disconnector = s return nil } // regConnect marks a subnetwork and instance as connected by a Step s. func (nr *subnetworkRegistry) regConnect(nName, iName string, s *Step) DError { nr.mx.Lock() defer nr.mx.Unlock() pre := fmt.Sprintf("step %q cannot connect instance %q to subnetwork %q", s.name, iName, nName) if im, _ := nr.connections[nName]; im == nil { nr.connections[nName] = map[string]*subnetworkConnection{iName: {connector: s}} } else if nc, _ := im[iName]; nc != nil && !s.nestedDepends(nc.disconnector) { return Errf("%s: concurrently connected by step %q", pre, nc.connector.name) } else { nr.connections[nName][iName] = &subnetworkConnection{connector: s} } return nil } func (nr *subnetworkRegistry) regDisconnect(nName, iName string, s *Step) DError { nr.mx.Lock() defer nr.mx.Unlock() return nr.disconnectHelper(nName, iName, s) } // regDisconnect all is called by Instance.regDelete and registers Step s as the disconnector for all subnetworks that iName is currently connected to. func (nr *subnetworkRegistry) regDisconnectAll(iName string, s *Step) DError { nr.mx.Lock() defer nr.mx.Unlock() var errs DError // For every subnetwork, if connected, disconnect. for nName, im := range nr.connections { if conn, _ := im[iName]; conn != nil && conn.disconnector == nil { errs = addErrs(nr.disconnectHelper(nName, iName, s)) } } return errs }