<commit_before>// Copyright 2019 Authors of Cilium
//
// 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 loader

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/cilium/cilium/common"
	"github.com/cilium/cilium/pkg/datapath"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/elf"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/serializer"

	"github.com/sirupsen/logrus"
)

var (
	once sync.Once

	// templateCache is the cache of pre-compiled datapaths.
	templateCache *objectCache

	ignoredELFPrefixes = []string{
		"2/",             // Calls within the endpoint
		"HOST_IP",        // Global
		"ROUTER_IP",      // Global
		"cilium_ct",      // All CT maps, including local
		"cilium_events",  // Global
		"cilium_ipcache", // Global
		"cilium_lb",      // Global
		"cilium_lxc",     // Global
		"cilium_metrics", // Global
		"cilium_proxy",   // Global
		"cilium_tunnel",  // Global
		"cilium_policy",  // Global
		"from-container", // Prog name
	}
)

// Init initializes the datapath cache with base program hashes derived from
// the LocalNodeConfiguration.
func Init(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) {
	once.Do(func() {
		templateCache = NewObjectCache(dp, nodeCfg)
		ignorePrefixes := ignoredELFPrefixes
		if !option.Config.EnableIPv4 {
			ignorePrefixes = append(ignorePrefixes, "LXC_IPV4")
		}
		if !option.Config.EnableIPv6 {
			ignorePrefixes = append(ignorePrefixes, "LXC_IP_")
		}
		elf.IgnoreSymbolPrefixes(ignorePrefixes)
	})
	templateCache.Update(nodeCfg)
}

// RestoreTemplates populates the object cache from templates on the filesystem
// at the specified path.
func RestoreTemplates(stateDir string) error {
	// Simplest implementation: Just garbage-collect everything.
	// In future we should make this smarter.
	err := os.RemoveAll(filepath.Join(stateDir, defaults.TemplatesDir))
	if os.IsNotExist(err) {
		return nil
	}
	return err
}

// objectCache is a map from a hash of the datapath to the path on the
// filesystem where its corresponding BPF object file exists.
type objectCache struct {
	lock.Mutex
	datapath.Datapath

	workingDirectory string
	baseHash         *datapathHash

	// toPath maps a hash to the filesystem path of the corresponding object.
	toPath map[string]string

	// compileQueue maps a hash to a queue which ensures that only one
	// attempt is made concurrently to compile the corresponding template.
	compileQueue map[string]*serializer.FunctionQueue
}

func newObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration, workingDir string) *objectCache {
	oc := &objectCache{
		Datapath:         dp,
		workingDirectory: workingDir,
		toPath:           make(map[string]string),
		compileQueue:     make(map[string]*serializer.FunctionQueue),
	}
	oc.Update(nodeCfg)
	return oc
}

// NewObjectCache creates a new cache for datapath objects, basing the hash
// upon the configuration of the datapath and the specified node configuration.
func NewObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) *objectCache {
	return newObjectCache(dp, nodeCfg, ".")
}

// Update may be called to update the base hash for configuration of datapath
// configuration that applies across the node.
func (o *objectCache) Update(nodeCfg *datapath.LocalNodeConfiguration) {
	newHash := hashDatapath(o.Datapath, nodeCfg, nil, nil)

	o.Lock()
	defer o.Unlock()
	o.baseHash = newHash
}

// serialize finds the channel that serializes builds against the same hash.
// Returns the channel and whether or not the caller needs to compile the
// datapath for this hash.
func (o *objectCache) serialize(hash string) (fq *serializer.FunctionQueue, found bool) {
	o.Lock()
	defer o.Unlock()

	fq, compiled := o.compileQueue[hash]
	if !compiled {
		fq = serializer.NewFunctionQueue(1)
		o.compileQueue[hash] = fq
	}
	return fq, compiled
}

func (o *objectCache) lookup(hash string) (string, bool) {
	o.Lock()
	defer o.Unlock()
	path, exists := o.toPath[hash]
	return path, exists
}

func (o *objectCache) insert(hash, objectPath string) {
	o.Lock()
	defer o.Unlock()
	o.toPath[hash] = objectPath
}

// build attempts to compile and cache a datapath template object file
// corresponding to the specified endpoint configuration.
func (o *objectCache) build(ctx context.Context, cfg *templateCfg, hash string) error {
	templatePath := filepath.Join(o.workingDirectory, defaults.TemplatesDir, hash)
	headerPath := filepath.Join(templatePath, common.CHeaderFileName)
	objectPath := filepath.Join(templatePath, endpointObj)

	if err := os.MkdirAll(templatePath, defaults.StateDirRights); err != nil {
		return err
	}

	f, err := os.Create(headerPath)
	if err != nil {
		return &os.PathError{
			Op:   "failed to open template header for writing",
			Path: headerPath,
			Err:  err,
		}
	}

	if err = o.Datapath.WriteEndpointConfig(f, cfg); err != nil {
		return &os.PathError{
			Op:   "failed to write template header",
			Path: headerPath,
			Err:  err,
		}
	}

	cfg.stats.bpfCompilation.Start()
	err = compileTemplate(ctx, templatePath)
	cfg.stats.bpfCompilation.End(err == nil)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.Path:               objectPath,
		logfields.BPFCompilationTime: cfg.stats.bpfCompilation.Total(),
	}).Info("Compiled new BPF template")

	o.insert(hash, objectPath)
	return nil
}

// fetchOrCompile attempts to fetch the path to the datapath object
// corresponding to the provided endpoint configuration, or if this
// configuration is not yet compiled, compiles it. It will block if multiple
// threads attempt to concurrently fetchOrCompile a template binary for the
// same set of EndpointConfiguration.
//
// Returns the path to the compiled template datapath object and whether the
// object was compiled, or an error.
func (o *objectCache) fetchOrCompile(ctx context.Context, cfg datapath.EndpointConfiguration, stats *SpanStat) (string, bool, error) {
	hash, err := o.baseHash.sumEndpoint(o, cfg, false)
	if err != nil {
		return "", false, err
	}

	// Serializes attempts to compile this cfg.
	fq, compiled := o.serialize(hash)
	if !compiled {
		fq.Enqueue(func() error {
			defer fq.Stop()
			templateCfg := wrap(cfg, stats)
			return o.build(ctx, templateCfg, hash)
		}, serializer.NoRetry)
	}

	// Wait until the build completes.
	if err = fq.Wait(ctx); err != nil {
		return "", false, fmt.Errorf("context cancelled while waiting for template compilation: %s", err)
	}

	// Fetch the result of the compilation.
	path, ok := o.lookup(hash)
	if !ok {
		return "", false, fmt.Errorf("peer compilation for this template failed")
	}

	return path, !compiled, nil
}

// EndpointHash hashes the specified endpoint configuration with the current
// datapath hash cache and returns the hash as string.
func EndpointHash(cfg datapath.EndpointConfiguration) (string, error) {
	return templateCache.baseHash.sumEndpoint(templateCache, cfg, true)
}
<commit_msg>loader: Improve error messages<commit_after>// Copyright 2019 Authors of Cilium
//
// 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 loader

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/cilium/cilium/common"
	"github.com/cilium/cilium/pkg/datapath"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/elf"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/serializer"

	"github.com/sirupsen/logrus"
)

var (
	once sync.Once

	// templateCache is the cache of pre-compiled datapaths.
	templateCache *objectCache

	ignoredELFPrefixes = []string{
		"2/",             // Calls within the endpoint
		"HOST_IP",        // Global
		"ROUTER_IP",      // Global
		"cilium_ct",      // All CT maps, including local
		"cilium_events",  // Global
		"cilium_ipcache", // Global
		"cilium_lb",      // Global
		"cilium_lxc",     // Global
		"cilium_metrics", // Global
		"cilium_proxy",   // Global
		"cilium_tunnel",  // Global
		"cilium_policy",  // Global
		"from-container", // Prog name
	}
)

// Init initializes the datapath cache with base program hashes derived from
// the LocalNodeConfiguration.
func Init(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) {
	once.Do(func() {
		templateCache = NewObjectCache(dp, nodeCfg)
		ignorePrefixes := ignoredELFPrefixes
		if !option.Config.EnableIPv4 {
			ignorePrefixes = append(ignorePrefixes, "LXC_IPV4")
		}
		if !option.Config.EnableIPv6 {
			ignorePrefixes = append(ignorePrefixes, "LXC_IP_")
		}
		elf.IgnoreSymbolPrefixes(ignorePrefixes)
	})
	templateCache.Update(nodeCfg)
}

// RestoreTemplates populates the object cache from templates on the filesystem
// at the specified path.
func RestoreTemplates(stateDir string) error {
	// Simplest implementation: Just garbage-collect everything.
	// In future we should make this smarter.
	path := filepath.Join(stateDir, defaults.TemplatesDir)
	err := os.RemoveAll(path)
	if os.IsNotExist(err) {
		return nil
	}
	return &os.PathError{
		Op:   "failed to remove old BPF templates",
		Path: path,
		Err:  err,
	}
}

// objectCache is a map from a hash of the datapath to the path on the
// filesystem where its corresponding BPF object file exists.
type objectCache struct {
	lock.Mutex
	datapath.Datapath

	workingDirectory string
	baseHash         *datapathHash

	// toPath maps a hash to the filesystem path of the corresponding object.
	toPath map[string]string

	// compileQueue maps a hash to a queue which ensures that only one
	// attempt is made concurrently to compile the corresponding template.
	compileQueue map[string]*serializer.FunctionQueue
}

func newObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration, workingDir string) *objectCache {
	oc := &objectCache{
		Datapath:         dp,
		workingDirectory: workingDir,
		toPath:           make(map[string]string),
		compileQueue:     make(map[string]*serializer.FunctionQueue),
	}
	oc.Update(nodeCfg)
	return oc
}

// NewObjectCache creates a new cache for datapath objects, basing the hash
// upon the configuration of the datapath and the specified node configuration.
func NewObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) *objectCache {
	return newObjectCache(dp, nodeCfg, ".")
}

// Update may be called to update the base hash for configuration of datapath
// configuration that applies across the node.
func (o *objectCache) Update(nodeCfg *datapath.LocalNodeConfiguration) {
	newHash := hashDatapath(o.Datapath, nodeCfg, nil, nil)

	o.Lock()
	defer o.Unlock()
	o.baseHash = newHash
}

// serialize finds the channel that serializes builds against the same hash.
// Returns the channel and whether or not the caller needs to compile the
// datapath for this hash.
func (o *objectCache) serialize(hash string) (fq *serializer.FunctionQueue, found bool) {
	o.Lock()
	defer o.Unlock()

	fq, compiled := o.compileQueue[hash]
	if !compiled {
		fq = serializer.NewFunctionQueue(1)
		o.compileQueue[hash] = fq
	}
	return fq, compiled
}

func (o *objectCache) lookup(hash string) (string, bool) {
	o.Lock()
	defer o.Unlock()
	path, exists := o.toPath[hash]
	return path, exists
}

func (o *objectCache) insert(hash, objectPath string) {
	o.Lock()
	defer o.Unlock()
	o.toPath[hash] = objectPath
}

// build attempts to compile and cache a datapath template object file
// corresponding to the specified endpoint configuration.
func (o *objectCache) build(ctx context.Context, cfg *templateCfg, hash string) error {
	templatePath := filepath.Join(o.workingDirectory, defaults.TemplatesDir, hash)
	headerPath := filepath.Join(templatePath, common.CHeaderFileName)
	objectPath := filepath.Join(templatePath, endpointObj)

	if err := os.MkdirAll(templatePath, defaults.StateDirRights); err != nil {
		return &os.PathError{
			Op:   "failed to create template directory",
			Path: templatePath,
			Err:  err,
		}
	}

	f, err := os.Create(headerPath)
	if err != nil {
		return &os.PathError{
			Op:   "failed to open template header for writing",
			Path: headerPath,
			Err:  err,
		}
	}

	if err = o.Datapath.WriteEndpointConfig(f, cfg); err != nil {
		return &os.PathError{
			Op:   "failed to write template header",
			Path: headerPath,
			Err:  err,
		}
	}

	cfg.stats.bpfCompilation.Start()
	err = compileTemplate(ctx, templatePath)
	cfg.stats.bpfCompilation.End(err == nil)
	if err != nil {
		return &os.PathError{
			Op:   "failed to compile template program",
			Path: templatePath,
			Err:  err,
		}
	}

	log.WithFields(logrus.Fields{
		logfields.Path:               objectPath,
		logfields.BPFCompilationTime: cfg.stats.bpfCompilation.Total(),
	}).Info("Compiled new BPF template")

	o.insert(hash, objectPath)
	return nil
}

// fetchOrCompile attempts to fetch the path to the datapath object
// corresponding to the provided endpoint configuration, or if this
// configuration is not yet compiled, compiles it. It will block if multiple
// threads attempt to concurrently fetchOrCompile a template binary for the
// same set of EndpointConfiguration.
//
// Returns the path to the compiled template datapath object and whether the
// object was compiled, or an error.
func (o *objectCache) fetchOrCompile(ctx context.Context, cfg datapath.EndpointConfiguration, stats *SpanStat) (string, bool, error) {
	hash, err := o.baseHash.sumEndpoint(o, cfg, false)
	if err != nil {
		return "", false, err
	}

	// Serializes attempts to compile this cfg.
	fq, compiled := o.serialize(hash)
	if !compiled {
		fq.Enqueue(func() error {
			defer fq.Stop()
			templateCfg := wrap(cfg, stats)
			return o.build(ctx, templateCfg, hash)
		}, serializer.NoRetry)
	}

	// Wait until the build completes.
	if err = fq.Wait(ctx); err != nil {
		return "", false, fmt.Errorf("BPF template compilation failed: %s", err)
	}

	// Fetch the result of the compilation.
	path, ok := o.lookup(hash)
	if !ok {
		return "", false, fmt.Errorf("BPF template compilation unsuccessful")
	}

	return path, !compiled, nil
}

// EndpointHash hashes the specified endpoint configuration with the current
// datapath hash cache and returns the hash as string.
func EndpointHash(cfg datapath.EndpointConfiguration) (string, error) {
	return templateCache.baseHash.sumEndpoint(templateCache, cfg, true)
}
<|endoftext|>


<commit_before>/*
Copyright © 2014 Brad Ackerman.

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 dbaccess

import "strings"

// Our hand-crafted SQL statements.
var (
	materialComposition = `
  SELECT mt.typeID AS materialID, m.quantity AS quantity
  FROM invTypes t, invTypes mt, invTypeMaterials m
  WHERE t.typeID = ?
  AND t.typeID = m.typeID
  AND mt.typeID = m.materialTypeID
  `
	itemInfo = `
  SELECT t.typeID, t.typeName, t.portionSize, g.groupName, c.categoryName
  FROM invTypes t, invCategories c, invGroups g
  WHERE t.typeName = ? AND t.groupID = g.groupID
  AND   g.categoryID = c.categoryID
  `
	itemIDInfo = `
  SELECT t.typeID, t.typeName, t.portionSize, g.groupName, c.categoryName
  FROM invTypes t, invCategories c, invGroups g
  WHERE t.typeID = ? AND t.groupID = g.groupID
  AND   g.categoryID = c.categoryID
  `
	catTree = `
  WITH RECURSIVE
  parents(marketGroupID, parentGroupID) AS
  (
    SELECT marketGroupID, parentGroupID FROM invMarketGroups
    WHERE marketGroupID = (
      SELECT marketGroupID
      FROM invTypes i
      JOIN invMarketGroups m USING(marketGroupID)
      WHERE i.typeID = ?
      )
      UNION ALL
      SELECT mg.marketGroupID, mg.parentGroupID
      FROM invMarketGroups mg
      INNER JOIN parents p ON mg.marketGroupID=p.parentGroupID
      )
      SELECT p.marketGroupID, m1.marketGroupName, m1.description, p.parentGroupID, m2.marketGroupName, m2.description
      FROM parents p
      JOIN invMarketGroups m1 ON p.marketGroupID = m1.marketGroupID
      JOIN invMarketGroups m2 ON p.parentGroupID = m2.marketGroupID
      `

	systemInfo = `
      SELECT   s.solarSystemName, s.solarSystemID, s.security,
               c.constellationName, c.constellationID, r.regionName, r.regionID
      FROM     mapSolarSystems s
      JOIN     mapConstellations c USING(constellationID)
      JOIN     mapRegions r USING(regionID)
      WHERE    LOWER(s.solarSystemName) LIKE LOWER(?)
			ORDER BY s.solarSystemName
      `

	systemIDInfo = `
      SELECT s.solarSystemName, s.solarSystemID, s.security,
             c.constellationName, c.constellationID, r.regionName, r.regionID
      FROM   mapSolarSystems s
      JOIN   mapConstellations c USING(constellationID)
      JOIN   mapRegions r USING(regionID)
      WHERE  s.solarSystemID = ?
      `

	regionInfo = `
      SELECT regionid, regionname
      FROM   mapregions
      WHERE  regionName = ?
      `

	stationIDInfo = `
      SELECT stationName, stationID, solarSystemID, constellationID, regionID
      FROM   staStations
      WHERE  stationID = ?
      `
	stationNameInfo = `
		SELECT stationName, stationID, solarSystemID, constellationID, regionID
		FROM   staStations
		WHERE  LOWER(stationName) LIKE LOWER(?)
		ORDER BY stationName
		`

	blueprintBase = `
		SELECT ti.typeName inputItem, ram.activityName, tyo.typeName outputProduct,
		       iap.quantity outputProductQty
		FROM   industryActivityProducts iap
		JOIN   invTypes ti USING(typeID)
		JOIN   ramActivities ram USING(activityID)
		JOIN   invTypes tyo ON iap.productTypeID = tyo.typeID
		WHERE  QUERYCOLUMN LIKE ?
		`

	// What items can I produce with a blueprint?
	blueprintProduces = strings.Replace(blueprintBase, "QUERYCOLUMN", "inputItem", 1)

	// How can I produce a blueprint?
	blueprintProducedBy = strings.Replace(blueprintBase, "QUERYCOLUMN", "outputProduct", 1)

	// Extra stanzas for WHERE when querying on input materials
	inputMatsWhere = `
		JOIN   industryActivityMaterials iam
		ON     iam.typeID = ti.typeID
		JOIN   invTypes tm
		ON     iam.materialTypeID = tm.typeID
	`
	inputMaterialsToBlueprint = strings.Replace(
		strings.Replace(blueprintBase, "WHERE", inputMatsWhere+" WHERE ", 1),
		"QUERYCOLUMN", "tm.typeName", 1)

	// Given a blueprint, what items do I need to manufacture/invent with it?
	materialsForBlueprintProduction = `
		SELECT ti.typeName inputItem, activityName, tm.typeName inputMaterial,
					 iam.quantity inputMaterialQty, consume, tyo.typeName outputProduct,
					 iap.quantity outputProductQty
		FROM   industryActivityMaterials iam
		JOIN   invTypes ti USING(typeID)
		JOIN   invTypes tm
		ON     iam.materialTypeID = tm.typeID
		JOIN   ramActivities USING(activityID)
		JOIN   industryActivityProducts iap
		ON     iap.typeID = ti.typeID AND iap.activityID=iam.activityID
		JOIN   invTypes tyo ON iap.productTypeID = tyo.typeID
		WHERE  inputItem = ? AND outputProduct = ?
		`
)
<commit_msg>Add ORDER BY to all SQL statements returning > 1 row.<commit_after>/*
Copyright © 2014 Brad Ackerman.

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 dbaccess

import "strings"

// Our hand-crafted SQL statements.
var (
	materialComposition = `
  SELECT mt.typeID AS materialID, m.quantity AS quantity
  FROM invTypes t, invTypes mt, invTypeMaterials m
  WHERE t.typeID = ?
  AND t.typeID = m.typeID
  AND mt.typeID = m.materialTypeID
  `
	itemInfo = `
  SELECT t.typeID, t.typeName, t.portionSize, g.groupName, c.categoryName
  FROM invTypes t, invCategories c, invGroups g
  WHERE t.typeName = ? AND t.groupID = g.groupID
  AND   g.categoryID = c.categoryID
  `
	itemIDInfo = `
  SELECT t.typeID, t.typeName, t.portionSize, g.groupName, c.categoryName
  FROM invTypes t, invCategories c, invGroups g
  WHERE t.typeID = ? AND t.groupID = g.groupID
  AND   g.categoryID = c.categoryID
  `
	catTree = `
  WITH RECURSIVE
  parents(marketGroupID, parentGroupID) AS
  (
    SELECT marketGroupID, parentGroupID FROM invMarketGroups
    WHERE marketGroupID = (
      SELECT marketGroupID
      FROM invTypes i
      JOIN invMarketGroups m USING(marketGroupID)
      WHERE i.typeID = ?
      )
      UNION ALL
      SELECT mg.marketGroupID, mg.parentGroupID
      FROM invMarketGroups mg
      INNER JOIN parents p ON mg.marketGroupID=p.parentGroupID
      )
      SELECT p.marketGroupID, m1.marketGroupName, m1.description, p.parentGroupID, m2.marketGroupName, m2.description
      FROM parents p
      JOIN invMarketGroups m1 ON p.marketGroupID = m1.marketGroupID
      JOIN invMarketGroups m2 ON p.parentGroupID = m2.marketGroupID
      `

	systemInfo = `
      SELECT   s.solarSystemName, s.solarSystemID, s.security,
               c.constellationName, c.constellationID, r.regionName, r.regionID
      FROM     mapSolarSystems s
      JOIN     mapConstellations c USING(constellationID)
      JOIN     mapRegions r USING(regionID)
      WHERE    LOWER(s.solarSystemName) LIKE LOWER(?)
			ORDER BY s.solarSystemName
      `

	systemIDInfo = `
      SELECT s.solarSystemName, s.solarSystemID, s.security,
             c.constellationName, c.constellationID, r.regionName, r.regionID
      FROM   mapSolarSystems s
      JOIN   mapConstellations c USING(constellationID)
      JOIN   mapRegions r USING(regionID)
      WHERE  s.solarSystemID = ?
      `

	regionInfo = `
      SELECT regionid, regionname
      FROM   mapregions
      WHERE  regionName = ?
      `

	stationIDInfo = `
      SELECT stationName, stationID, solarSystemID, constellationID, regionID
      FROM   staStations
      WHERE  stationID = ?
      `
	stationNameInfo = `
		SELECT stationName, stationID, solarSystemID, constellationID, regionID
		FROM   staStations
		WHERE  LOWER(stationName) LIKE LOWER(?)
		ORDER BY stationName
		`

	blueprintBase = `
		SELECT ti.typeName inputItem, ram.activityName, tyo.typeName outputProduct,
		       iap.quantity outputProductQty
		FROM   industryActivityProducts iap
		JOIN   invTypes ti USING(typeID)
		JOIN   ramActivities ram USING(activityID)
		JOIN   invTypes tyo ON iap.productTypeID = tyo.typeID
		WHERE  QUERYCOLUMN LIKE ?
		ORDER BY inputItem, outputProduct
		`

	// What items can I produce with a blueprint?
	blueprintProduces = strings.Replace(blueprintBase, "QUERYCOLUMN", "inputItem", 1)

	// How can I produce a blueprint?
	blueprintProducedBy = strings.Replace(blueprintBase, "QUERYCOLUMN", "outputProduct", 1)

	// Extra stanzas for WHERE when querying on input materials
	inputMatsWhere = `
		JOIN   industryActivityMaterials iam
		ON     iam.typeID = ti.typeID
		JOIN   invTypes tm
		ON     iam.materialTypeID = tm.typeID
	`
	inputMaterialsToBlueprint = strings.Replace(
		strings.Replace(blueprintBase, "WHERE", inputMatsWhere+" WHERE ", 1),
		"QUERYCOLUMN", "tm.typeName", 1)

	// Given a blueprint, what items do I need to manufacture/invent with it?
	materialsForBlueprintProduction = `
		SELECT ti.typeName inputItem, activityName, tm.typeName inputMaterial,
					 iam.quantity inputMaterialQty, consume, tyo.typeName outputProduct,
					 iap.quantity outputProductQty
		FROM   industryActivityMaterials iam
		JOIN   invTypes ti USING(typeID)
		JOIN   invTypes tm
		ON     iam.materialTypeID = tm.typeID
		JOIN   ramActivities USING(activityID)
		JOIN   industryActivityProducts iap
		ON     iap.typeID = ti.typeID AND iap.activityID=iam.activityID
		JOIN   invTypes tyo ON iap.productTypeID = tyo.typeID
		WHERE  inputItem = ? AND outputProduct = ?
		ORDER BY inputItem, outputProduct, inputMaterial
		`
)
<|endoftext|>


<commit_before>package httphelper

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"reflect"
	"syscall"
	"time"

	"github.com/flynn/flynn/pkg/cors"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/random"
	"github.com/jackc/pgx"
	"github.com/julienschmidt/httprouter"
	"golang.org/x/net/context"
)

type ErrorCode string

const (
	NotFoundErrorCode           ErrorCode = "not_found"
	ObjectNotFoundErrorCode     ErrorCode = "object_not_found"
	ObjectExistsErrorCode       ErrorCode = "object_exists"
	ConflictErrorCode           ErrorCode = "conflict"
	SyntaxErrorCode             ErrorCode = "syntax_error"
	ValidationErrorCode         ErrorCode = "validation_error"
	PreconditionFailedErrorCode ErrorCode = "precondition_failed"
	UnauthorizedErrorCode       ErrorCode = "unauthorized"
	UnknownErrorCode            ErrorCode = "unknown_error"
	RatelimitedErrorCode        ErrorCode = "ratelimited"
	ServiceUnavailableErrorCode ErrorCode = "service_unavailable"
)

var errorResponseCodes = map[ErrorCode]int{
	NotFoundErrorCode:           404,
	ObjectNotFoundErrorCode:     404,
	ObjectExistsErrorCode:       409,
	ConflictErrorCode:           409,
	PreconditionFailedErrorCode: 412,
	SyntaxErrorCode:             400,
	ValidationErrorCode:         400,
	UnauthorizedErrorCode:       401,
	UnknownErrorCode:            500,
	RatelimitedErrorCode:        429,
	ServiceUnavailableErrorCode: 503,
}

type JSONError struct {
	Code    ErrorCode       `json:"code"`
	Message string          `json:"message"`
	Detail  json.RawMessage `json:"detail,omitempty"`
	Retry   bool            `json:"retry"`
}

func isJSONErrorWithCode(err error, code ErrorCode) bool {
	e, ok := err.(JSONError)
	return ok && e.Code == code
}

func IsObjectNotFoundError(err error) bool {
	return isJSONErrorWithCode(err, ObjectNotFoundErrorCode)
}

func IsObjectExistsError(err error) bool {
	return isJSONErrorWithCode(err, ObjectExistsErrorCode)
}

func IsPreconditionFailedError(err error) bool {
	return isJSONErrorWithCode(err, PreconditionFailedErrorCode)
}

func IsValidationError(err error) bool {
	return isJSONErrorWithCode(err, ValidationErrorCode)
}

// IsRetryableError indicates whether a HTTP request can be safely retried.
func IsRetryableError(err error) bool {
	e, ok := err.(JSONError)
	return ok && e.Retry
}

var CORSAllowAll = &cors.Options{
	AllowAllOrigins:  true,
	AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
	AllowHeaders:     []string{"Authorization", "Accept", "Content-Type", "If-Match", "If-None-Match"},
	ExposeHeaders:    []string{"ETag", "Content-Disposition"},
	AllowCredentials: true,
	MaxAge:           time.Hour,
}

// Handler is an extended version of http.Handler that also takes a context
// argument ctx.
type Handler interface {
	ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
}

// The HandlerFunc type is an adapter to allow the use of ordinary functions as
// Handlers.  If f is a function with the appropriate signature, HandlerFunc(f)
// is a Handler object that calls f.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP calls f(ctx, w, r).
func (f HandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	f(ctx, w, r)
}

func WrapHandler(handler HandlerFunc) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		ctx := contextFromResponseWriter(w)
		ctx = ctxhelper.NewContextParams(ctx, params)
		handler.ServeHTTP(ctx, w, req)
	}
}

func ContextInjector(componentName string, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		reqID := req.Header.Get("X-Request-ID")
		if reqID == "" {
			reqID = random.UUID()
		}
		ctx := ctxhelper.NewContextRequestID(context.Background(), reqID)
		ctx = ctxhelper.NewContextComponentName(ctx, componentName)
		rw := NewResponseWriter(w, ctx)
		handler.ServeHTTP(rw, req)
	})
}

func contextFromResponseWriter(w http.ResponseWriter) context.Context {
	ctx := w.(*ResponseWriter).Context()
	return ctx
}

func (jsonError JSONError) Error() string {
	return fmt.Sprintf("%s: %s", jsonError.Code, jsonError.Message)
}

func logError(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); ok {
		logger, _ := ctxhelper.LoggerFromContext(rw.Context())
		logger.Error(err.Error())
	} else {
		log.Println(err)
	}
}

// buildJSONError returns an appropriate API error to send to clients based
// on the given internal error.
//
// We consider all postgres errors as retry-able as they usually occur when
// postgres is read-only (for example during a system update). Data related
// postgres errors should in general not be retried (because a retry will
// likely result in the same error), but it is expected that such errors are
// caught and a validation error returned to the client rather than the
// postgres error.
//
// Errors returned from "net" are also considered retryable because they
// generally occur when the process is trying to reach another resource which
// may be down temporarily.
// This also includes syscall.Errno, which is notable because it can also be
// returned from file operations among other things.
// It's expected if you don't want clients to retry on these errors they
// should be caught and a more appropriate error returned to the caller.
func buildJSONError(err error) *JSONError {
	jsonError := &JSONError{
		Code:    UnknownErrorCode,
		Message: "Something went wrong",
	}
	switch v := err.(type) {
	case *json.SyntaxError, *json.UnmarshalTypeError:
		jsonError = &JSONError{
			Code:    SyntaxErrorCode,
			Message: "The provided JSON input is invalid",
		}
	case pgx.PgError, *net.OpError, syscall.Errno:
		jsonError.Retry = true
	case JSONError:
		jsonError = &v
	case *JSONError:
		jsonError = v
	default:
		if err == pgx.ErrDeadConn {
			jsonError.Retry = true
		}
	}
	return jsonError
}

func Error(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); !ok || (ok && rw.Status() == 0) {
		jsonError := buildJSONError(err)
		if jsonError.Code == UnknownErrorCode {
			logError(w, err)
		}
		responseCode, ok := errorResponseCodes[jsonError.Code]
		if !ok {
			responseCode = 500
		}
		JSON(w, responseCode, jsonError)
	} else {
		logError(w, err)
	}
}

func ObjectNotFoundError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ObjectNotFoundErrorCode, Message: message})
}

func ObjectExistsErr(message string) error {
	return JSONError{Code: ObjectExistsErrorCode, Message: message}
}

func ObjectExistsError(w http.ResponseWriter, message string) {
	Error(w, ObjectExistsErr(message))
}

func ConflictError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ConflictErrorCode, Message: message})
}

func PreconditionFailedErr(message string) error {
	return JSONError{Code: PreconditionFailedErrorCode, Message: message}
}

func ServiceUnavailableError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ServiceUnavailableErrorCode, Message: message, Retry: true})
}

func ValidationError(w http.ResponseWriter, field, message string) {
	err := JSONError{Code: ValidationErrorCode, Message: message}
	if field != "" {
		err.Message = fmt.Sprintf("%s %s", field, message)
		err.Detail, _ = json.Marshal(map[string]string{"field": field})
	}
	Error(w, err)
}

func JSON(w http.ResponseWriter, status int, v interface{}) {
	// Encode nil slices as `[]` instead of `null`
	if rv := reflect.ValueOf(v); rv.Type().Kind() == reflect.Slice && rv.IsNil() {
		v = []struct{}{}
	}

	var result []byte
	var err error
	result, err = json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(result)
}

func DecodeJSON(req *http.Request, i interface{}) error {
	dec := json.NewDecoder(req.Body)
	dec.UseNumber()
	return dec.Decode(i)
}
<commit_msg>pkg/httphelper: Add RetryClient<commit_after>package httphelper

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"reflect"
	"syscall"
	"time"

	"github.com/flynn/flynn/pkg/cors"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/dialer"
	"github.com/flynn/flynn/pkg/random"
	"github.com/jackc/pgx"
	"github.com/julienschmidt/httprouter"
	"golang.org/x/net/context"
)

type ErrorCode string

var RetryClient = &http.Client{Transport: &http.Transport{Dial: dialer.Retry.Dial}}

const (
	NotFoundErrorCode           ErrorCode = "not_found"
	ObjectNotFoundErrorCode     ErrorCode = "object_not_found"
	ObjectExistsErrorCode       ErrorCode = "object_exists"
	ConflictErrorCode           ErrorCode = "conflict"
	SyntaxErrorCode             ErrorCode = "syntax_error"
	ValidationErrorCode         ErrorCode = "validation_error"
	PreconditionFailedErrorCode ErrorCode = "precondition_failed"
	UnauthorizedErrorCode       ErrorCode = "unauthorized"
	UnknownErrorCode            ErrorCode = "unknown_error"
	RatelimitedErrorCode        ErrorCode = "ratelimited"
	ServiceUnavailableErrorCode ErrorCode = "service_unavailable"
)

var errorResponseCodes = map[ErrorCode]int{
	NotFoundErrorCode:           404,
	ObjectNotFoundErrorCode:     404,
	ObjectExistsErrorCode:       409,
	ConflictErrorCode:           409,
	PreconditionFailedErrorCode: 412,
	SyntaxErrorCode:             400,
	ValidationErrorCode:         400,
	UnauthorizedErrorCode:       401,
	UnknownErrorCode:            500,
	RatelimitedErrorCode:        429,
	ServiceUnavailableErrorCode: 503,
}

type JSONError struct {
	Code    ErrorCode       `json:"code"`
	Message string          `json:"message"`
	Detail  json.RawMessage `json:"detail,omitempty"`
	Retry   bool            `json:"retry"`
}

func isJSONErrorWithCode(err error, code ErrorCode) bool {
	e, ok := err.(JSONError)
	return ok && e.Code == code
}

func IsObjectNotFoundError(err error) bool {
	return isJSONErrorWithCode(err, ObjectNotFoundErrorCode)
}

func IsObjectExistsError(err error) bool {
	return isJSONErrorWithCode(err, ObjectExistsErrorCode)
}

func IsPreconditionFailedError(err error) bool {
	return isJSONErrorWithCode(err, PreconditionFailedErrorCode)
}

func IsValidationError(err error) bool {
	return isJSONErrorWithCode(err, ValidationErrorCode)
}

// IsRetryableError indicates whether a HTTP request can be safely retried.
func IsRetryableError(err error) bool {
	e, ok := err.(JSONError)
	return ok && e.Retry
}

var CORSAllowAll = &cors.Options{
	AllowAllOrigins:  true,
	AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
	AllowHeaders:     []string{"Authorization", "Accept", "Content-Type", "If-Match", "If-None-Match"},
	ExposeHeaders:    []string{"ETag", "Content-Disposition"},
	AllowCredentials: true,
	MaxAge:           time.Hour,
}

// Handler is an extended version of http.Handler that also takes a context
// argument ctx.
type Handler interface {
	ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
}

// The HandlerFunc type is an adapter to allow the use of ordinary functions as
// Handlers.  If f is a function with the appropriate signature, HandlerFunc(f)
// is a Handler object that calls f.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP calls f(ctx, w, r).
func (f HandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	f(ctx, w, r)
}

func WrapHandler(handler HandlerFunc) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		ctx := contextFromResponseWriter(w)
		ctx = ctxhelper.NewContextParams(ctx, params)
		handler.ServeHTTP(ctx, w, req)
	}
}

func ContextInjector(componentName string, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		reqID := req.Header.Get("X-Request-ID")
		if reqID == "" {
			reqID = random.UUID()
		}
		ctx := ctxhelper.NewContextRequestID(context.Background(), reqID)
		ctx = ctxhelper.NewContextComponentName(ctx, componentName)
		rw := NewResponseWriter(w, ctx)
		handler.ServeHTTP(rw, req)
	})
}

func contextFromResponseWriter(w http.ResponseWriter) context.Context {
	ctx := w.(*ResponseWriter).Context()
	return ctx
}

func (jsonError JSONError) Error() string {
	return fmt.Sprintf("%s: %s", jsonError.Code, jsonError.Message)
}

func logError(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); ok {
		logger, _ := ctxhelper.LoggerFromContext(rw.Context())
		logger.Error(err.Error())
	} else {
		log.Println(err)
	}
}

// buildJSONError returns an appropriate API error to send to clients based
// on the given internal error.
//
// We consider all postgres errors as retry-able as they usually occur when
// postgres is read-only (for example during a system update). Data related
// postgres errors should in general not be retried (because a retry will
// likely result in the same error), but it is expected that such errors are
// caught and a validation error returned to the client rather than the
// postgres error.
//
// Errors returned from "net" are also considered retryable because they
// generally occur when the process is trying to reach another resource which
// may be down temporarily.
// This also includes syscall.Errno, which is notable because it can also be
// returned from file operations among other things.
// It's expected if you don't want clients to retry on these errors they
// should be caught and a more appropriate error returned to the caller.
func buildJSONError(err error) *JSONError {
	jsonError := &JSONError{
		Code:    UnknownErrorCode,
		Message: "Something went wrong",
	}
	switch v := err.(type) {
	case *json.SyntaxError, *json.UnmarshalTypeError:
		jsonError = &JSONError{
			Code:    SyntaxErrorCode,
			Message: "The provided JSON input is invalid",
		}
	case pgx.PgError, *net.OpError, syscall.Errno:
		jsonError.Retry = true
	case JSONError:
		jsonError = &v
	case *JSONError:
		jsonError = v
	default:
		if err == pgx.ErrDeadConn {
			jsonError.Retry = true
		}
	}
	return jsonError
}

func Error(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); !ok || (ok && rw.Status() == 0) {
		jsonError := buildJSONError(err)
		if jsonError.Code == UnknownErrorCode {
			logError(w, err)
		}
		responseCode, ok := errorResponseCodes[jsonError.Code]
		if !ok {
			responseCode = 500
		}
		JSON(w, responseCode, jsonError)
	} else {
		logError(w, err)
	}
}

func ObjectNotFoundError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ObjectNotFoundErrorCode, Message: message})
}

func ObjectExistsErr(message string) error {
	return JSONError{Code: ObjectExistsErrorCode, Message: message}
}

func ObjectExistsError(w http.ResponseWriter, message string) {
	Error(w, ObjectExistsErr(message))
}

func ConflictError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ConflictErrorCode, Message: message})
}

func PreconditionFailedErr(message string) error {
	return JSONError{Code: PreconditionFailedErrorCode, Message: message}
}

func ServiceUnavailableError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ServiceUnavailableErrorCode, Message: message, Retry: true})
}

func ValidationError(w http.ResponseWriter, field, message string) {
	err := JSONError{Code: ValidationErrorCode, Message: message}
	if field != "" {
		err.Message = fmt.Sprintf("%s %s", field, message)
		err.Detail, _ = json.Marshal(map[string]string{"field": field})
	}
	Error(w, err)
}

func JSON(w http.ResponseWriter, status int, v interface{}) {
	// Encode nil slices as `[]` instead of `null`
	if rv := reflect.ValueOf(v); rv.Type().Kind() == reflect.Slice && rv.IsNil() {
		v = []struct{}{}
	}

	var result []byte
	var err error
	result, err = json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(result)
}

func DecodeJSON(req *http.Request, i interface{}) error {
	dec := json.NewDecoder(req.Body)
	dec.UseNumber()
	return dec.Decode(i)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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 cmd

import (
	"fmt"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	autoscalingv1 "k8s.io/api/autoscaling/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/printers"
	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
	autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
	"k8s.io/kubernetes/pkg/api/legacyscheme"
	"k8s.io/kubernetes/pkg/kubectl"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	autoscaleLong = templates.LongDesc(i18n.T(`
		Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.

		Looks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.
		An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))

	autoscaleExample = templates.Examples(i18n.T(`
		# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used:
		kubectl autoscale deployment foo --min=2 --max=10

		# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%:
		kubectl autoscale rc foo --max=5 --cpu-percent=80`))
)

type AutoscaleOptions struct {
	FilenameOptions *resource.FilenameOptions

	RecordFlags *genericclioptions.RecordFlags
	Recorder    genericclioptions.Recorder

	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Name       string
	Generator  string
	Min        int32
	Max        int32
	CpuPercent int32

	createAnnotation bool
	args             []string
	enforceNamespace bool
	namespace        string
	dryRun           bool
	builder          *resource.Builder
	canBeAutoscaled  polymorphichelpers.CanBeAutoscaledFunc
	generatorFunc    func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error)

	HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter

	genericclioptions.IOStreams
}

func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
	return &AutoscaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
		FilenameOptions: &resource.FilenameOptions{},
		RecordFlags:     genericclioptions.NewRecordFlags(),
		Recorder:        genericclioptions.NoopRecorder{},

		IOStreams: ioStreams,
	}
}

func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAutoscaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller"}

	cmd := &cobra.Command{
		Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("Auto-scale a Deployment, ReplicaSet, or ReplicationController"),
		Long:    autoscaleLong,
		Example: autoscaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	// bind flag structs
	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().StringVar(&o.Generator, "generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
	cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
	cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
	cmd.MarkFlagRequired("max")
	cmd.Flags().Int32Var(&o.CpuPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
	cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
	cmdutil.AddDryRunFlag(cmd)
	cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
	cmdutil.AddApplyAnnotationFlags(cmd)
	return cmd
}

func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.dryRun = cmdutil.GetFlagBool(cmd, "dry-run")
	o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
	o.builder = f.NewBuilder()
	o.canBeAutoscaled = polymorphichelpers.CanBeAutoscaledFn
	o.args = args
	o.RecordFlags.Complete(cmd)

	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}

	kubeClient, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.HPAClient = kubeClient.AutoscalingV1()

	// get the generator
	o.generatorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) {
		switch o.Generator {
		case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
			return &kubectl.HorizontalPodAutoscalerGeneratorV1{
				Name:               name,
				MinReplicas:        o.Min,
				MaxReplicas:        o.Max,
				CPUPercent:         o.CpuPercent,
				ScaleRefName:       name,
				ScaleRefKind:       mapping.GroupVersionKind.Kind,
				ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
			}, nil
		default:
			return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", o.Generator)
		}
	}

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		if o.dryRun {
			o.PrintFlags.Complete("%s (dry run)")
		}

		return o.PrintFlags.ToPrinter()
	}

	return nil
}

func (o *AutoscaleOptions) Validate() error {
	if o.Max < 1 {
		return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
	}
	if o.Max < o.Min {
		return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
	}

	return nil
}

func (o *AutoscaleOptions) Run() error {
	r := o.builder.
		WithScheme(legacyscheme.Scheme).
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, o.FilenameOptions).
		ResourceTypeOrNameArgs(false, o.args...).
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	count := 0
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		if err := o.canBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
			return err
		}

		generator, err := o.generatorFunc(info.Name, mapping)
		if err != nil {
			return err
		}

		// Generate new object
		object, err := generator.StructuredGenerate()
		if err != nil {
			return err
		}
		hpa, ok := object.(*autoscalingv1.HorizontalPodAutoscaler)
		if !ok {
			return fmt.Errorf("generator made %T, not autoscalingv1.HorizontalPodAutoscaler", object)
		}

		if err := o.Recorder.Record(hpa); err != nil {
			glog.V(4).Infof("error recording current command: %v", err)
		}

		if o.dryRun {
			count++

			printer, err := o.ToPrinter("created")
			if err != nil {
				return err
			}
			return printer.PrintObj(hpa, o.Out)
		}

		if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa, cmdutil.InternalVersionJSONEncoder()); err != nil {
			return err
		}

		actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(hpa)
		if err != nil {
			return err
		}

		count++
		printer, err := o.ToPrinter("autoscaled")
		if err != nil {
			return err
		}
		return printer.PrintObj(actualHPA, o.Out)
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to autoscale")
	}
	return nil
}
<commit_msg>Replace internal version Encoder with external version Encoder for autoscale.<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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 cmd

import (
	"fmt"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	autoscalingv1 "k8s.io/api/autoscaling/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/printers"
	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
	autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
	"k8s.io/kubernetes/pkg/kubectl"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	autoscaleLong = templates.LongDesc(i18n.T(`
		Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.

		Looks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.
		An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))

	autoscaleExample = templates.Examples(i18n.T(`
		# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used:
		kubectl autoscale deployment foo --min=2 --max=10

		# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%:
		kubectl autoscale rc foo --max=5 --cpu-percent=80`))
)

type AutoscaleOptions struct {
	FilenameOptions *resource.FilenameOptions

	RecordFlags *genericclioptions.RecordFlags
	Recorder    genericclioptions.Recorder

	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Name       string
	Generator  string
	Min        int32
	Max        int32
	CpuPercent int32

	createAnnotation bool
	args             []string
	enforceNamespace bool
	namespace        string
	dryRun           bool
	builder          *resource.Builder
	canBeAutoscaled  polymorphichelpers.CanBeAutoscaledFunc
	generatorFunc    func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error)

	HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter

	genericclioptions.IOStreams
}

func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
	return &AutoscaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
		FilenameOptions: &resource.FilenameOptions{},
		RecordFlags:     genericclioptions.NewRecordFlags(),
		Recorder:        genericclioptions.NoopRecorder{},

		IOStreams: ioStreams,
	}
}

func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAutoscaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller"}

	cmd := &cobra.Command{
		Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("Auto-scale a Deployment, ReplicaSet, or ReplicationController"),
		Long:    autoscaleLong,
		Example: autoscaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	// bind flag structs
	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().StringVar(&o.Generator, "generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
	cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
	cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
	cmd.MarkFlagRequired("max")
	cmd.Flags().Int32Var(&o.CpuPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
	cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
	cmdutil.AddDryRunFlag(cmd)
	cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
	cmdutil.AddApplyAnnotationFlags(cmd)
	return cmd
}

func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.dryRun = cmdutil.GetFlagBool(cmd, "dry-run")
	o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
	o.builder = f.NewBuilder()
	o.canBeAutoscaled = polymorphichelpers.CanBeAutoscaledFn
	o.args = args
	o.RecordFlags.Complete(cmd)

	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}

	kubeClient, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.HPAClient = kubeClient.AutoscalingV1()

	// get the generator
	o.generatorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) {
		switch o.Generator {
		case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
			return &kubectl.HorizontalPodAutoscalerGeneratorV1{
				Name:               name,
				MinReplicas:        o.Min,
				MaxReplicas:        o.Max,
				CPUPercent:         o.CpuPercent,
				ScaleRefName:       name,
				ScaleRefKind:       mapping.GroupVersionKind.Kind,
				ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
			}, nil
		default:
			return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", o.Generator)
		}
	}

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		if o.dryRun {
			o.PrintFlags.Complete("%s (dry run)")
		}

		return o.PrintFlags.ToPrinter()
	}

	return nil
}

func (o *AutoscaleOptions) Validate() error {
	if o.Max < 1 {
		return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
	}
	if o.Max < o.Min {
		return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
	}

	return nil
}

func (o *AutoscaleOptions) Run() error {
	r := o.builder.
		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, o.FilenameOptions).
		ResourceTypeOrNameArgs(false, o.args...).
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	count := 0
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		if err := o.canBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
			return err
		}

		generator, err := o.generatorFunc(info.Name, mapping)
		if err != nil {
			return err
		}

		// Generate new object
		object, err := generator.StructuredGenerate()
		if err != nil {
			return err
		}
		hpa, ok := object.(*autoscalingv1.HorizontalPodAutoscaler)
		if !ok {
			return fmt.Errorf("generator made %T, not autoscalingv1.HorizontalPodAutoscaler", object)
		}

		if err := o.Recorder.Record(hpa); err != nil {
			glog.V(4).Infof("error recording current command: %v", err)
		}

		if o.dryRun {
			count++

			printer, err := o.ToPrinter("created")
			if err != nil {
				return err
			}
			return printer.PrintObj(hpa, o.Out)
		}

		if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
			return err
		}

		actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(hpa)
		if err != nil {
			return err
		}

		count++
		printer, err := o.ToPrinter("autoscaled")
		if err != nil {
			return err
		}
		return printer.PrintObj(actualHPA, o.Out)
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to autoscale")
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Istio Authors
//
// 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 snapshot

import (
	"sync"
	"time"

	mcp "istio.io/api/mcp/v1alpha1"
	"istio.io/istio/pkg/log"
	"istio.io/istio/pkg/mcp/server"
)

var scope = log.RegisterScope("snapshot", "mcp snapshot", 0)

// Snapshot provides an immutable view of versioned envelopes.
type Snapshot interface {
	Resources(typ string) []*mcp.Envelope
	Version(typ string) string
}

// Cache is a snapshot-based cache that maintains a single versioned
// snapshot of responses per client. Cache consistently replies with the
// latest snapshot.
type Cache struct {
	mu         sync.RWMutex
	snapshots  map[string]Snapshot
	status     map[string]*statusInfo
	watchCount int64
}

// New creates a new cache of resource snapshots.
func New() *Cache {
	return &Cache{
		snapshots: make(map[string]Snapshot),
		status:    make(map[string]*statusInfo),
	}
}

var _ server.Watcher = &Cache{}

type responseWatch struct {
	request   *mcp.MeshConfigRequest // original request
	responseC chan<- *server.WatchResponse
}

type statusInfo struct {
	mu                   sync.Mutex
	client               *mcp.Client
	lastWatchRequestTime time.Time // informational
	watches              map[int64]*responseWatch
}

// Watch returns a watch for an MCP request.
func (c *Cache) Watch(request *mcp.MeshConfigRequest, responseC chan<- *server.WatchResponse) (*server.WatchResponse, server.CancelWatchFunc) { // nolint: lll
	// TODO(ayj) - use hash of clients's ID to index map.
	nodeID := request.Client.GetId()

	c.mu.Lock()
	defer c.mu.Unlock()

	info, ok := c.status[nodeID]
	if !ok {
		info = &statusInfo{
			client:  request.Client,
			watches: make(map[int64]*responseWatch),
		}
		c.status[nodeID] = info
	}

	// update last responseWatch request time
	info.mu.Lock()
	info.lastWatchRequestTime = time.Now()
	info.mu.Unlock()

	// return an immediate response if a snapshot is available and the
	// requested version doesn't match.
	if snapshot, ok := c.snapshots[nodeID]; ok {
		version := snapshot.Version(request.TypeUrl)
		scope.Debugf("Found snapshot for node: %q, with version: %q", nodeID, version)
		if version != request.VersionInfo {
			scope.Debugf("Responding to node %q with snapshot:\n%v\n", nodeID, snapshot)
			response := &server.WatchResponse{
				TypeURL:   request.TypeUrl,
				Version:   version,
				Envelopes: snapshot.Resources(request.TypeUrl),
			}
			return response, nil
		}
	}

	// Otherwise, open a watch if no snapshot was available or the requested version is up-to-date.
	c.watchCount++
	watchID := c.watchCount

	log.Infof("Watch(): created watch %d for %s from nodeID %q, version1 %q",
		watchID, request.TypeUrl, nodeID, request.VersionInfo)

	info.mu.Lock()
	info.watches[watchID] = &responseWatch{request: request, responseC: responseC}
	info.mu.Unlock()

	cancel := func() {
		c.mu.Lock()
		defer c.mu.Unlock()
		if info, ok := c.status[nodeID]; ok {
			info.mu.Lock()
			delete(info.watches, watchID)
			info.mu.Unlock()
		}
	}
	return nil, cancel
}

// SetSnapshot updates a snapshot for a client.
func (c *Cache) SetSnapshot(node string, snapshot Snapshot) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// update the existing entry
	c.snapshots[node] = snapshot

	// trigger existing watches for which version1 changed
	if info, ok := c.status[node]; ok {
		info.mu.Lock()
		for id, watch := range info.watches {
			version := snapshot.Version(watch.request.TypeUrl)
			if version != watch.request.VersionInfo {
				log.Infof("SetSnapshot(): respond to watch %d with new version1 %q", id, version)

				response := &server.WatchResponse{
					TypeURL:   watch.request.TypeUrl,
					Version:   version,
					Envelopes: snapshot.Resources(watch.request.TypeUrl),
				}
				watch.responseC <- response

				// discard the responseWatch
				delete(info.watches, id)
			}
		}
		info.mu.Unlock()
	}
}

// ClearSnapshot clears snapshot for a client. This does not cancel any open
// watches already created (see ClearStatus).
func (c *Cache) ClearSnapshot(node string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	delete(c.snapshots, node)
}

// ClearStatus clears status for a client. This has the effect of canceling
// any open watches opened against this client info.
func (c *Cache) ClearStatus(node string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if info, ok := c.status[node]; ok {
		info.mu.Lock()
		for _, watch := range info.watches {
			// response channel may be shared
			watch.responseC <- nil
		}
		info.mu.Unlock()
	}
	delete(c.status, node)
}
<commit_msg>fix typo in snapshot log/comments (#7853)<commit_after>// Copyright 2018 Istio Authors
//
// 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 snapshot

import (
	"sync"
	"time"

	mcp "istio.io/api/mcp/v1alpha1"
	"istio.io/istio/pkg/log"
	"istio.io/istio/pkg/mcp/server"
)

var scope = log.RegisterScope("snapshot", "mcp snapshot", 0)

// Snapshot provides an immutable view of versioned envelopes.
type Snapshot interface {
	Resources(typ string) []*mcp.Envelope
	Version(typ string) string
}

// Cache is a snapshot-based cache that maintains a single versioned
// snapshot of responses per client. Cache consistently replies with the
// latest snapshot.
type Cache struct {
	mu         sync.RWMutex
	snapshots  map[string]Snapshot
	status     map[string]*statusInfo
	watchCount int64
}

// New creates a new cache of resource snapshots.
func New() *Cache {
	return &Cache{
		snapshots: make(map[string]Snapshot),
		status:    make(map[string]*statusInfo),
	}
}

var _ server.Watcher = &Cache{}

type responseWatch struct {
	request   *mcp.MeshConfigRequest // original request
	responseC chan<- *server.WatchResponse
}

type statusInfo struct {
	mu                   sync.Mutex
	client               *mcp.Client
	lastWatchRequestTime time.Time // informational
	watches              map[int64]*responseWatch
}

// Watch returns a watch for an MCP request.
func (c *Cache) Watch(request *mcp.MeshConfigRequest, responseC chan<- *server.WatchResponse) (*server.WatchResponse, server.CancelWatchFunc) { // nolint: lll
	// TODO(ayj) - use hash of clients's ID to index map.
	nodeID := request.Client.GetId()

	c.mu.Lock()
	defer c.mu.Unlock()

	info, ok := c.status[nodeID]
	if !ok {
		info = &statusInfo{
			client:  request.Client,
			watches: make(map[int64]*responseWatch),
		}
		c.status[nodeID] = info
	}

	// update last responseWatch request time
	info.mu.Lock()
	info.lastWatchRequestTime = time.Now()
	info.mu.Unlock()

	// return an immediate response if a snapshot is available and the
	// requested version doesn't match.
	if snapshot, ok := c.snapshots[nodeID]; ok {
		version := snapshot.Version(request.TypeUrl)
		scope.Debugf("Found snapshot for node: %q, with version: %q", nodeID, version)
		if version != request.VersionInfo {
			scope.Debugf("Responding to node %q with snapshot:\n%v\n", nodeID, snapshot)
			response := &server.WatchResponse{
				TypeURL:   request.TypeUrl,
				Version:   version,
				Envelopes: snapshot.Resources(request.TypeUrl),
			}
			return response, nil
		}
	}

	// Otherwise, open a watch if no snapshot was available or the requested version is up-to-date.
	c.watchCount++
	watchID := c.watchCount

	log.Infof("Watch(): created watch %d for %s from nodeID %q, version %q",
		watchID, request.TypeUrl, nodeID, request.VersionInfo)

	info.mu.Lock()
	info.watches[watchID] = &responseWatch{request: request, responseC: responseC}
	info.mu.Unlock()

	cancel := func() {
		c.mu.Lock()
		defer c.mu.Unlock()
		if info, ok := c.status[nodeID]; ok {
			info.mu.Lock()
			delete(info.watches, watchID)
			info.mu.Unlock()
		}
	}
	return nil, cancel
}

// SetSnapshot updates a snapshot for a client.
func (c *Cache) SetSnapshot(node string, snapshot Snapshot) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// update the existing entry
	c.snapshots[node] = snapshot

	// trigger existing watches for which version changed
	if info, ok := c.status[node]; ok {
		info.mu.Lock()
		for id, watch := range info.watches {
			version := snapshot.Version(watch.request.TypeUrl)
			if version != watch.request.VersionInfo {
				log.Infof("SetSnapshot(): respond to watch %d with new version %q", id, version)

				response := &server.WatchResponse{
					TypeURL:   watch.request.TypeUrl,
					Version:   version,
					Envelopes: snapshot.Resources(watch.request.TypeUrl),
				}
				watch.responseC <- response

				// discard the responseWatch
				delete(info.watches, id)
			}
		}
		info.mu.Unlock()
	}
}

// ClearSnapshot clears snapshot for a client. This does not cancel any open
// watches already created (see ClearStatus).
func (c *Cache) ClearSnapshot(node string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	delete(c.snapshots, node)
}

// ClearStatus clears status for a client. This has the effect of canceling
// any open watches opened against this client info.
func (c *Cache) ClearStatus(node string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if info, ok := c.status[node]; ok {
		info.mu.Lock()
		for _, watch := range info.watches {
			// response channel may be shared
			watch.responseC <- nil
		}
		info.mu.Unlock()
	}
	delete(c.status, node)
}
<|endoftext|>


<commit_before>package middleware

import (
	"strconv"
	"strings"

	"github.com/Unknwon/macaron"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/apikeygen"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
)

type Context struct {
	*macaron.Context
	*m.SignedInUser

	Session SessionStore

	IsSignedIn     bool
	AllowAnonymous bool
}

func GetContextHandler() macaron.Handler {
	return func(c *macaron.Context) {
		ctx := &Context{
			Context:        c,
			SignedInUser:   &m.SignedInUser{},
			Session:        GetSession(),
			IsSignedIn:     false,
			AllowAnonymous: false,
		}

		// the order in which these are tested are important
		// look for api key in Authorization header first
		// then init session and look for userId in session
		// then look for api key in session (special case for render calls via api)
		// then test if anonymous access is enabled
		if initContextWithApiKey(ctx) ||
			initContextWithUserSessionCookie(ctx) ||
			initContextWithApiKeyFromSession(ctx) ||
			initContextWithAnonymousUser(ctx) {
		}

		c.Map(ctx)
	}
}

func initContextWithAnonymousUser(ctx *Context) bool {
	if !setting.AnonymousEnabled {
		return false
	}

	orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
	if err := bus.Dispatch(&orgQuery); err != nil {
		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
		return false
	} else {
		ctx.IsSignedIn = false
		ctx.AllowAnonymous = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
		ctx.OrgId = orgQuery.Result.Id
		ctx.OrgName = orgQuery.Result.Name
		return true
	}
}

func initContextWithUserSessionCookie(ctx *Context) bool {
	// initialize session
	if err := ctx.Session.Start(ctx); err != nil {
		log.Error(3, "Failed to start session", err)
		return false
	}

	var userId int64
	if userId = getRequestUserId(ctx); userId == 0 {
		return false
	}

	query := m.GetSignedInUserQuery{UserId: userId}
	if err := bus.Dispatch(&query); err != nil {
		return false
	} else {
		ctx.SignedInUser = query.Result
		ctx.IsSignedIn = true
		return true
	}
}

func initContextWithApiKey(ctx *Context) bool {
	var keyString string
	if keyString = getApiKey(ctx); keyString == "" {
		return false
	}

	// base64 decode key
	decoded, err := apikeygen.Decode(keyString)
	if err != nil {
		ctx.JsonApiErr(401, "Invalid API key", err)
		return true
	}
	// fetch key
	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
	if err := bus.Dispatch(&keyQuery); err != nil {
		ctx.JsonApiErr(401, "Invalid API key", err)
		return true
	} else {
		apikey := keyQuery.Result

		// validate api key
		if !apikeygen.IsValid(decoded, apikey.Key) {
			ctx.JsonApiErr(401, "Invalid API key", err)
			return true
		}

		ctx.IsSignedIn = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = apikey.Role
		ctx.ApiKeyId = apikey.Id
		ctx.OrgId = apikey.OrgId
		return true
	}
}

// special case for panel render calls with api key
func initContextWithApiKeyFromSession(ctx *Context) bool {
	keyId := ctx.Session.Get(SESS_KEY_APIKEY)
	if keyId == nil {
		return false
	}

	keyQuery := m.GetApiKeyByIdQuery{ApiKeyId: keyId.(int64)}
	if err := bus.Dispatch(&keyQuery); err != nil {
		log.Error(3, "Failed to get api key by id", err)
		return false
	} else {
		apikey := keyQuery.Result

		ctx.IsSignedIn = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = apikey.Role
		ctx.ApiKeyId = apikey.Id
		ctx.OrgId = apikey.OrgId
		return true
	}
}

// Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) {
	if err != nil {
		log.Error(4, "%s: %v", title, err)
		if setting.Env != setting.PROD {
			ctx.Data["ErrorMsg"] = err
		}
	}

	switch status {
	case 200:
		metrics.M_Page_Status_200.Inc(1)
	case 404:
		metrics.M_Page_Status_404.Inc(1)
	case 500:
		metrics.M_Page_Status_500.Inc(1)
	}

	ctx.Data["Title"] = title
	ctx.HTML(status, strconv.Itoa(status))
}

func (ctx *Context) JsonOK(message string) {
	resp := make(map[string]interface{})

	resp["message"] = message

	ctx.JSON(200, resp)
}

func (ctx *Context) IsApiRequest() bool {
	return strings.HasPrefix(ctx.Req.URL.Path, "/api")
}

func (ctx *Context) JsonApiErr(status int, message string, err error) {
	resp := make(map[string]interface{})

	if err != nil {
		log.Error(4, "%s: %v", message, err)
		if setting.Env != setting.PROD {
			resp["error"] = err.Error()
		}
	}

	switch status {
	case 404:
		resp["message"] = "Not Found"
		metrics.M_Api_Status_500.Inc(1)
	case 500:
		metrics.M_Api_Status_404.Inc(1)
		resp["message"] = "Internal Server Error"
	}

	if message != "" {
		resp["message"] = message
	}

	ctx.JSON(status, resp)
}
<commit_msg>Enforce domain<commit_after>package middleware

import (
	"strconv"
	"strings"

	"github.com/Unknwon/macaron"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/apikeygen"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
)

type Context struct {
	*macaron.Context
	*m.SignedInUser

	Session SessionStore

	IsSignedIn     bool
	AllowAnonymous bool
}

func GetContextHandler() macaron.Handler {
	return func(c *macaron.Context) {
		ctx := &Context{
			Context:        c,
			SignedInUser:   &m.SignedInUser{},
			Session:        GetSession(),
			IsSignedIn:     false,
			AllowAnonymous: false,
		}

		h := ctx.Req.Host
		if i := strings.Index(h, ":"); i >= 0 {
			h = h[:i]
		}

		if !strings.EqualFold(h, setting.Domain) {
			ctx.Redirect(strings.TrimSuffix(setting.AppUrl, "/") + ctx.Req.RequestURI, 301)
			return
		}

		// the order in which these are tested are important
		// look for api key in Authorization header first
		// then init session and look for userId in session
		// then look for api key in session (special case for render calls via api)
		// then test if anonymous access is enabled
		if initContextWithApiKey(ctx) ||
			initContextWithUserSessionCookie(ctx) ||
			initContextWithApiKeyFromSession(ctx) ||
			initContextWithAnonymousUser(ctx) {
		}

		c.Map(ctx)
	}
}

func initContextWithAnonymousUser(ctx *Context) bool {
	if !setting.AnonymousEnabled {
		return false
	}

	orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
	if err := bus.Dispatch(&orgQuery); err != nil {
		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
		return false
	} else {
		ctx.IsSignedIn = false
		ctx.AllowAnonymous = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
		ctx.OrgId = orgQuery.Result.Id
		ctx.OrgName = orgQuery.Result.Name
		return true
	}
}

func initContextWithUserSessionCookie(ctx *Context) bool {
	// initialize session
	if err := ctx.Session.Start(ctx); err != nil {
		log.Error(3, "Failed to start session", err)
		return false
	}

	var userId int64
	if userId = getRequestUserId(ctx); userId == 0 {
		return false
	}

	query := m.GetSignedInUserQuery{UserId: userId}
	if err := bus.Dispatch(&query); err != nil {
		return false
	} else {
		ctx.SignedInUser = query.Result
		ctx.IsSignedIn = true
		return true
	}
}

func initContextWithApiKey(ctx *Context) bool {
	var keyString string
	if keyString = getApiKey(ctx); keyString == "" {
		return false
	}

	// base64 decode key
	decoded, err := apikeygen.Decode(keyString)
	if err != nil {
		ctx.JsonApiErr(401, "Invalid API key", err)
		return true
	}
	// fetch key
	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
	if err := bus.Dispatch(&keyQuery); err != nil {
		ctx.JsonApiErr(401, "Invalid API key", err)
		return true
	} else {
		apikey := keyQuery.Result

		// validate api key
		if !apikeygen.IsValid(decoded, apikey.Key) {
			ctx.JsonApiErr(401, "Invalid API key", err)
			return true
		}

		ctx.IsSignedIn = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = apikey.Role
		ctx.ApiKeyId = apikey.Id
		ctx.OrgId = apikey.OrgId
		return true
	}
}

// special case for panel render calls with api key
func initContextWithApiKeyFromSession(ctx *Context) bool {
	keyId := ctx.Session.Get(SESS_KEY_APIKEY)
	if keyId == nil {
		return false
	}

	keyQuery := m.GetApiKeyByIdQuery{ApiKeyId: keyId.(int64)}
	if err := bus.Dispatch(&keyQuery); err != nil {
		log.Error(3, "Failed to get api key by id", err)
		return false
	} else {
		apikey := keyQuery.Result

		ctx.IsSignedIn = true
		ctx.SignedInUser = &m.SignedInUser{}
		ctx.OrgRole = apikey.Role
		ctx.ApiKeyId = apikey.Id
		ctx.OrgId = apikey.OrgId
		return true
	}
}

// Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) {
	if err != nil {
		log.Error(4, "%s: %v", title, err)
		if setting.Env != setting.PROD {
			ctx.Data["ErrorMsg"] = err
		}
	}

	switch status {
	case 200:
		metrics.M_Page_Status_200.Inc(1)
	case 404:
		metrics.M_Page_Status_404.Inc(1)
	case 500:
		metrics.M_Page_Status_500.Inc(1)
	}

	ctx.Data["Title"] = title
	ctx.HTML(status, strconv.Itoa(status))
}

func (ctx *Context) JsonOK(message string) {
	resp := make(map[string]interface{})

	resp["message"] = message

	ctx.JSON(200, resp)
}

func (ctx *Context) IsApiRequest() bool {
	return strings.HasPrefix(ctx.Req.URL.Path, "/api")
}

func (ctx *Context) JsonApiErr(status int, message string, err error) {
	resp := make(map[string]interface{})

	if err != nil {
		log.Error(4, "%s: %v", message, err)
		if setting.Env != setting.PROD {
			resp["error"] = err.Error()
		}
	}

	switch status {
	case 404:
		resp["message"] = "Not Found"
		metrics.M_Api_Status_500.Inc(1)
	case 500:
		metrics.M_Api_Status_404.Inc(1)
		resp["message"] = "Internal Server Error"
	}

	if message != "" {
		resp["message"] = message
	}

	ctx.JSON(status, resp)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors

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 reconciler

import (
	"context"

	"github.com/kelseyhightower/envconfig"
	"k8s.io/client-go/tools/cache"

	"knative.dev/eventing/pkg/apis/sources/v1alpha1"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/resolver"

	eventingclient "knative.dev/eventing/pkg/client/injection/client"
	eventtypeinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1alpha1/eventtype"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	deploymentinformer "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment"
	samplesourceClient "knative.dev/sample-source/pkg/client/injection/client"
	samplesourceinformer "knative.dev/sample-source/pkg/client/injection/informers/samples/v1alpha1/samplesource"
	"knative.dev/sample-source/pkg/client/injection/reconciler/samples/v1alpha1/samplesource"
)

// NewController initializes the controller and is called by the generated code
// Registers event handlers to enqueue events
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {
	deploymentInformer := deploymentinformer.Get(ctx)
	sampleSourceInformer := samplesourceinformer.Get(ctx)
	eventTypeInformer := eventtypeinformer.Get(ctx)

	r := &Reconciler{
		KubeClientSet:         kubeclient.Get(ctx),
		EventingClientSet:     eventingclient.Get(ctx),
		samplesourceLister:    sampleSourceInformer.Lister(),
		deploymentLister:      deploymentInformer.Lister(),
		samplesourceClientSet: samplesourceClient.Get(ctx),
	}
	if err := envconfig.Process("", r); err != nil {
		logging.FromContext(ctx).Panicf("required environment variable is not defined: %v", err)
	}

	impl := samplesource.NewImpl(ctx, r)
	r.sinkResolver = resolver.NewURIResolver(ctx, impl.EnqueueKey)

	logging.FromContext(ctx).Info("Setting up event handlers")
	sampleSourceInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))

	deploymentInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("SampleSource")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	eventTypeInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.Filter(v1alpha1.SchemeGroupVersion.WithKind("SampleSource")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	return impl
}
<commit_msg>Drop use of deprecated filter API (#31)<commit_after>/*
Copyright 2019 The Knative Authors

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 reconciler

import (
	"context"

	"github.com/kelseyhightower/envconfig"
	"k8s.io/client-go/tools/cache"

	"knative.dev/eventing/pkg/apis/sources/v1alpha1"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/resolver"

	eventingclient "knative.dev/eventing/pkg/client/injection/client"
	eventtypeinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1alpha1/eventtype"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	deploymentinformer "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment"
	samplesourceClient "knative.dev/sample-source/pkg/client/injection/client"
	samplesourceinformer "knative.dev/sample-source/pkg/client/injection/informers/samples/v1alpha1/samplesource"
	"knative.dev/sample-source/pkg/client/injection/reconciler/samples/v1alpha1/samplesource"
)

// NewController initializes the controller and is called by the generated code
// Registers event handlers to enqueue events
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {
	deploymentInformer := deploymentinformer.Get(ctx)
	sampleSourceInformer := samplesourceinformer.Get(ctx)
	eventTypeInformer := eventtypeinformer.Get(ctx)

	r := &Reconciler{
		KubeClientSet:         kubeclient.Get(ctx),
		EventingClientSet:     eventingclient.Get(ctx),
		samplesourceLister:    sampleSourceInformer.Lister(),
		deploymentLister:      deploymentInformer.Lister(),
		samplesourceClientSet: samplesourceClient.Get(ctx),
	}
	if err := envconfig.Process("", r); err != nil {
		logging.FromContext(ctx).Panicf("required environment variable is not defined: %v", err)
	}

	impl := samplesource.NewImpl(ctx, r)
	r.sinkResolver = resolver.NewURIResolver(ctx, impl.EnqueueKey)

	logging.FromContext(ctx).Info("Setting up event handlers")
	sampleSourceInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))

	deploymentInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterGroupKind(v1alpha1.Kind("SampleSource")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	eventTypeInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: controller.FilterGroupKind(v1alpha1.Kind("SampleSource")),
		Handler:    controller.HandleAll(impl.EnqueueControllerOf),
	})

	return impl
}
<|endoftext|>


<commit_before>package routefinder

// Package routefinder implements functions for decomposing known dynamic URLs into templates.

import (
	"fmt"
	"regexp"
	"strings"
)

// Routes represents an ordered table of URL-like route templates, allowing
// reverse lookup of these into the original template along with parsed-out
// variables.
type Routes []struct {
	name string
	re   regexp.Regexp
}

// NewRoutefinder constructs a Route-table from the given templates. Everything
// in the template from a : to the following / is considered a variable.
func NewRoutefinder(templates ...string) (Routes, error) {
	converterRegex, err := regexp.Compile(":[^/]+")

	if err != nil {
		return Routes{}, err
	}

	// Regex placeholders out of the template URLs
	routes := make(Routes, len(templates))

	for i, template := range templates {
		// Quote slashes &c.
		withQuotedMeta := regexp.QuoteMeta(template)

		// Switch out :[^\/]+ for capture groups
		withQuotedMeta = converterRegex.ReplaceAllStringFunc(withQuotedMeta, func(group string) string {
			return fmt.Sprintf("(?P<%s>[^/]+)", group[1:])
		})

		// Add start and end guards
		withQuotedMeta = fmt.Sprintf("^%s$", withQuotedMeta)

		re, err := regexp.Compile(withQuotedMeta)

		if err != nil {
			return Routes{}, err
		}

		routes[i].name = template
		routes[i].re = *re
	}

	return routes, nil
}

// Lookup the given path in the available Routes, first-match-wins.  A match
// will return the original template string along with a map of the parsed-out
// variables. Lookup returns empty values, if no match is found.
func (r Routes) Lookup(path string) (string, map[string]string) {
	// Dump any query string
	normalizedPath := strings.SplitN(path, "?", 1)[0]

	// Check key against regex'es
	for _, template := range r {
		// Check key against regexes
		if !template.re.MatchString(normalizedPath) {
			continue
		}

		subMatchNames := template.re.SubexpNames()
		subMatchValues := template.re.FindStringSubmatch(normalizedPath)

		meta := make(map[string]string, len(subMatchNames))

		for j := 1; j < len(subMatchNames); j += 1 {
			meta[subMatchNames[j]] = subMatchValues[j]
		}

		return template.name, meta
	}

	return "", map[string]string{}
}
<commit_msg>Make lint happier.<commit_after>package routefinder

// Package routefinder implements functions for decomposing known dynamic URLs into templates.

import (
	"fmt"
	"regexp"
	"strings"
)

// Routes represents an ordered table of URL-like route templates, allowing
// reverse lookup of these into the original template along with parsed-out
// variables.
type Routes []struct {
	name string
	re   regexp.Regexp
}

// NewRoutefinder constructs a Route-table from the given templates. Everything
// in the template from a : to the following / is considered a variable.
func NewRoutefinder(templates ...string) (Routes, error) {
	converterRegex, err := regexp.Compile(":[^/]+")

	if err != nil {
		return Routes{}, err
	}

	// Regex placeholders out of the template URLs
	routes := make(Routes, len(templates))

	for i, template := range templates {
		// Quote slashes &c.
		withQuotedMeta := regexp.QuoteMeta(template)

		// Switch out :[^\/]+ for capture groups
		withQuotedMeta = converterRegex.ReplaceAllStringFunc(withQuotedMeta, func(group string) string {
			return fmt.Sprintf("(?P<%s>[^/]+)", group[1:])
		})

		// Add start and end guards
		withQuotedMeta = fmt.Sprintf("^%s$", withQuotedMeta)

		re, err := regexp.Compile(withQuotedMeta)

		if err != nil {
			return Routes{}, err
		}

		routes[i].name = template
		routes[i].re = *re
	}

	return routes, nil
}

// Lookup the given path in the available Routes, first-match-wins.  A match
// will return the original template string along with a map of the parsed-out
// variables. Lookup returns empty values, if no match is found.
func (r Routes) Lookup(path string) (string, map[string]string) {
	// Dump any query string
	normalizedPath := strings.SplitN(path, "?", 1)[0]

	// Check key against regex'es
	for _, template := range r {
		// Check key against regexes
		if !template.re.MatchString(normalizedPath) {
			continue
		}

		subMatchNames := template.re.SubexpNames()
		subMatchValues := template.re.FindStringSubmatch(normalizedPath)

		meta := make(map[string]string, len(subMatchNames))

		for j := 1; j < len(subMatchNames); j++ {
			meta[subMatchNames[j]] = subMatchValues[j]
		}

		return template.name, meta
	}

	return "", map[string]string{}
}
<|endoftext|>