blob: 741d9bdecc3c10b5c83d377f5b26228f20afa7c5 [file] [log] [blame]
// 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
}