| // Copyright 2018 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 fakes | |
| import ( | |
| "context" | |
| "log" | |
| "net" | |
| "net/http" | |
| "net/http/httptest" | |
| "os" | |
| "strings" | |
| "testing" | |
| "cloud.google.com/go/compute/metadata" | |
| ) | |
| // MDS contains data and functionality for a fake compute Metadata Server. | |
| // It is intended to be constructed with NewMetadataServer. | |
| // | |
| // The MDS struct represents the state of the fake MDS instance. | |
| // Fields on this struct can be modified to influence the | |
| // return values of MDS API calls. | |
| // | |
| // The fake MDS server implements a small part of the API discussed here: | |
| // https://cloud.google.com/compute/docs/metadata/overview#instance-metadata. | |
| // Only the parts that we need for testing are implemented here. | |
| // | |
| // This struct should not be considered concurrency safe. | |
| type MDS struct { | |
| // Attributes represents attributes for the GCE instance stored by the metadata | |
| // server. | |
| Attributes map[string]string | |
| // Client is the client to use when accessing the fake MDS. | |
| Client *metadata.Client | |
| // Server is the fake MDS. It uses state from this struct for serving requests. | |
| Server *httptest.Server | |
| } | |
| func (m *MDS) instanceAttrHandler(w http.ResponseWriter, r *http.Request) { | |
| switch r.Method { | |
| case "GET": | |
| splitPath := strings.Split(r.URL.Path, "/") | |
| attributeKey := splitPath[len(splitPath)-1] | |
| data, ok := m.Attributes[attributeKey] | |
| if !ok { | |
| writeError(w, r, http.StatusNotFound) | |
| return | |
| } | |
| if _, err := w.Write([]byte(data)); err != nil { | |
| log.Printf("write %q failed: %v", r.URL.Path, err) | |
| } | |
| default: | |
| writeError(w, r, http.StatusNotFound) | |
| return | |
| } | |
| } | |
| // NewMetadataServer constructs a fake metadata server (MDS) implementation. | |
| func NewMetadataServer(ctx context.Context) (*MDS, error) { | |
| mds := &MDS{make(map[string]string), nil, nil} | |
| mux := http.NewServeMux() | |
| mux.HandleFunc("/computeMetadata/v1/instance/attributes/", mds.instanceAttrHandler) | |
| mds.Server = httptest.NewServer(mux) | |
| // The MDS uses a fixed IP to serve metadata, set this env variable to redirect | |
| // to fake server. | |
| err := os.Setenv("GCE_METADATA_HOST", mds.Server.Listener.Addr().String()) | |
| if err != nil { | |
| return nil, err | |
| } | |
| transport := &http.Transport{ | |
| DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | |
| return net.Dial(network, addr) | |
| }, | |
| } | |
| hc := &http.Client{Transport: transport} | |
| mds.Client = metadata.NewClient(hc) | |
| return mds, nil | |
| } | |
| func MDSForTest(t *testing.T) *MDS { | |
| t.Helper() | |
| mds, err := NewMetadataServer(context.Background()) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return mds | |
| } |