network.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 ( networkURLRegex = regexp.MustCompile(fmt.Sprintf(`^(projects/(?P<project>%[1]s)/)?global/networks/(?P<network>%[2]s)$`, projectRgxStr, rfc1035)) ) func (w *Workflow) networkExists(project, network string) (bool, DError) { return w.networkCache.resourceExists(func(project string, opts ...daisyCompute.ListCallOption) (interface{}, error) { return w.ComputeClient.ListNetworks(project) }, project, network) } // Network is used to create a GCE network. type Network struct { compute.Network AutoCreateSubnetworks *bool `json:"autoCreateSubnetworks,omitempty"` Resource } // MarshalJSON is a hacky workaround to compute.Network's implementation. func (n *Network) MarshalJSON() ([]byte, error) { return json.Marshal(*n) } func (n *Network) populate(ctx context.Context, s *Step) DError { var errs DError n.Name, errs = n.Resource.populateWithGlobal(ctx, s, n.Name) n.Description = strOr(n.Description, defaultDescription("Network", s.w.Name, s.w.username)) n.link = fmt.Sprintf("projects/%s/global/networks/%s", n.Project, n.Name) if n.AutoCreateSubnetworks != nil { n.Network.AutoCreateSubnetworks = *n.AutoCreateSubnetworks n.Network.ForceSendFields = []string{"AutoCreateSubnetworks"} } return errs } func (n *Network) validate(ctx context.Context, s *Step) DError { pre := fmt.Sprintf("cannot create network %q", n.daisyName) errs := n.Resource.validate(ctx, s, pre) if n.IPv4Range != "" { if _, _, err := net.ParseCIDR(n.IPv4Range); err != nil { errs = addErrs(errs, Errf("%s: bad IPv4Range: %q, error: %v", pre, n.IPv4Range, err)) } } modes := []string{"REGIONAL", "GLOBAL"} if n.RoutingConfig != nil && !strIn(n.RoutingConfig.RoutingMode, modes) { errs = addErrs(errs, Errf("%s: RoutingConfig %q not one of %v", pre, n.RoutingConfig.RoutingMode, modes)) } // Register creation. errs = addErrs(errs, s.w.networks.regCreate(n.daisyName, &n.Resource, s, false)) return errs } type networkConnection struct { connector, disconnector *Step } type networkRegistry struct { baseResourceRegistry connections map[string]map[string]*networkConnection testDisconnectHelper func(nName, iName string, s *Step) DError } func newNetworkRegistry(w *Workflow) *networkRegistry { nr := &networkRegistry{baseResourceRegistry: baseResourceRegistry{w: w, typeName: "network", urlRgx: networkURLRegex}} nr.baseResourceRegistry.deleteFn = nr.deleteFn nr.connections = map[string]map[string]*networkConnection{} nr.init() return nr } func (nr *networkRegistry) deleteFn(res *Resource) DError { m := NamedSubexp(networkURLRegex, res.link) err := nr.w.ComputeClient.DeleteNetwork(m["project"], m["network"]) if gErr, ok := err.(*googleapi.Error); ok && gErr.Code == http.StatusNotFound { return typedErr(resourceDNEError, "failed to delete network", err) } return newErr("failed to delete network", err) } func (nr *networkRegistry) 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 network %q", s.name, iName, nName) var conn *networkConnection 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 network and instance as connected by a Step s. func (nr *networkRegistry) regConnect(nName, iName string, s *Step) DError { nr.mx.Lock() defer nr.mx.Unlock() pre := fmt.Sprintf("step %q cannot connect instance %q to network %q", s.name, iName, nName) if im, _ := nr.connections[nName]; im == nil { nr.connections[nName] = map[string]*networkConnection{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] = &networkConnection{connector: s} } return nil } func (nr *networkRegistry) 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 networks that iName is currently connected to. func (nr *networkRegistry) regDisconnectAll(iName string, s *Step) DError { nr.mx.Lock() defer nr.mx.Unlock() var errs DError // For every network, 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 }