cns/restserver/restserver.go (316 lines of code) (raw):
package restserver
import (
"context"
"net"
"net/http"
"net/http/pprof"
"sync"
"time"
"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/cns/common"
"github.com/Azure/azure-container-networking/cns/dockerclient"
"github.com/Azure/azure-container-networking/cns/logger"
"github.com/Azure/azure-container-networking/cns/networkcontainers"
"github.com/Azure/azure-container-networking/cns/nodesubnet"
"github.com/Azure/azure-container-networking/cns/routes"
"github.com/Azure/azure-container-networking/cns/types"
"github.com/Azure/azure-container-networking/cns/types/bounded"
"github.com/Azure/azure-container-networking/cns/wireserver"
acn "github.com/Azure/azure-container-networking/common"
nma "github.com/Azure/azure-container-networking/nmagent"
"github.com/Azure/azure-container-networking/store"
"github.com/pkg/errors"
)
// This file contains the initialization of RestServer.
// all HTTP APIs - api.go and/or ipam.go
// APIs for internal consumption - internalapi.go
// All helper/utility functions - util.go
// Constants - const.go
// Named Lock for accessing different states in httpRestServiceState
var namedLock = acn.InitNamedLock()
type interfaceGetter interface {
GetInterfaces(ctx context.Context) (*wireserver.GetInterfacesResult, error)
}
type nmagentClient interface {
SupportedAPIs(context.Context) ([]string, error)
GetNCVersionList(context.Context) (nma.NCVersionList, error)
GetHomeAz(context.Context) (nma.AzResponse, error)
GetInterfaceIPInfo(ctx context.Context) (nma.Interfaces, error)
}
type wireserverProxy interface {
JoinNetwork(ctx context.Context, vnetID string) (*http.Response, error)
PublishNC(ctx context.Context, ncParams cns.NetworkContainerParameters, payload []byte) (*http.Response, error)
UnpublishNC(ctx context.Context, ncParams cns.NetworkContainerParameters, payload []byte) (*http.Response, error)
}
type imdsClient interface {
GetVMUniqueID(ctx context.Context) (string, error)
}
type iptablesClient interface {
ChainExists(table string, chain string) (bool, error)
NewChain(table string, chain string) error
Append(table string, chain string, rulespec ...string) error
Exists(table string, chain string, rulespec ...string) (bool, error)
Insert(table string, chain string, pos int, rulespec ...string) error
}
type iptablesGetter interface {
GetIPTables() (iptablesClient, error)
}
// HTTPRestService represents http listener for CNS - Container Networking Service.
type HTTPRestService struct {
*cns.Service
dockerClient *dockerclient.Client
wscli interfaceGetter
iptables iptablesGetter
nma nmagentClient
wsproxy wireserverProxy
homeAzMonitor *HomeAzMonitor
networkContainer *networkcontainers.NetworkContainers
PodIPIDByPodInterfaceKey map[string][]string // PodInterfaceId is key and value is slice of Pod IP (SecondaryIP) uuids.
PodIPConfigState map[string]cns.IPConfigurationStatus // Secondary IP ID(uuid) is key
routingTable *routes.RoutingTable
store store.KeyValueStore
state *httpRestServiceState
podsPendingIPAssignment *bounded.TimedSet
sync.RWMutex
dncPartitionKey string
EndpointState map[string]*EndpointInfo // key : container id
EndpointStateStore store.KeyValueStore
cniConflistGenerator CNIConflistGenerator
generateCNIConflistOnce sync.Once
IPConfigsHandlerMiddleware cns.IPConfigsHandlerMiddleware
PnpIDByMacAddress map[string]string
imdsClient imdsClient
nodesubnetIPFetcher *nodesubnet.IPFetcher
}
type CNIConflistGenerator interface {
Generate() error
Close() error
}
type NoOpConflistGenerator struct{}
func (*NoOpConflistGenerator) Generate() error {
return nil
}
func (*NoOpConflistGenerator) Close() error {
return nil
}
type EndpointInfo struct {
PodName string
PodNamespace string
IfnameToIPMap map[string]*IPInfo // key : interface name, value : IPInfo
}
type IPInfo struct {
IPv4 []net.IPNet
IPv6 []net.IPNet `json:",omitempty"`
HnsEndpointID string `json:",omitempty"`
HnsNetworkID string `json:",omitempty"`
HostVethName string `json:",omitempty"`
MacAddress string `json:",omitempty"`
NICType cns.NICType
}
type GetHTTPServiceDataResponse struct {
HTTPRestServiceData HTTPRestServiceData `json:"HTTPRestServiceData"`
Response Response `json:"Response"`
}
// HTTPRestServiceData represents in-memory CNS data in the debug API paths.
// TODO: add json tags for this struct as per linter suggestion, ignored for now as part of revert-PR
type HTTPRestServiceData struct { //nolint:musttag // not tagging struct for revert-PR
PodIPIDByPodInterfaceKey map[string][]string // PodInterfaceId is key and value is slice of Pod IP uuids.
PodIPConfigState map[string]cns.IPConfigurationStatus // secondaryipid(uuid) is key
}
type Response struct {
ReturnCode types.ResponseCode
Message string
}
// GetEndpointResponse describes response from the The GetEndpoint API.
type GetEndpointResponse struct {
Response Response `json:"response"`
EndpointInfo EndpointInfo `json:"endpointInfo"`
}
// containerstatus is used to save status of an existing container
type containerstatus struct {
ID string
VMVersion string
HostVersion string
CreateNetworkContainerRequest cns.CreateNetworkContainerRequest
VfpUpdateComplete bool // True when VFP programming is completed for the NC
}
// httpRestServiceState contains the state we would like to persist.
type httpRestServiceState struct {
Location string
NetworkType string
OrchestratorType string
NodeID string
Initialized bool
ContainerIDByOrchestratorContext map[string]*ncList // OrchestratorContext is the key and value is a list of NetworkContainerIDs separated by comma
ContainerStatus map[string]containerstatus // NetworkContainerID is key.
Networks map[string]*networkInfo
TimeStamp time.Time
joinedNetworks map[string]struct{}
primaryInterface *wireserver.InterfaceInfo
PnpIDByMacAddress map[string]string
}
type networkInfo struct {
NetworkName string
NicInfo *wireserver.InterfaceInfo
Options map[string]interface{}
}
// NewHTTPRestService creates a new HTTP Service object.
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsproxy wireserverProxy, iptg iptablesGetter, nmagentClient nmagentClient,
endpointStateStore store.KeyValueStore, gen CNIConflistGenerator, homeAzMonitor *HomeAzMonitor,
imdsClient imdsClient,
) (*HTTPRestService, error) {
service, err := cns.NewService(config.Name, config.Version, config.ChannelMode, config.Store)
if err != nil {
return nil, err
}
routingTable := &routes.RoutingTable{}
nc := &networkcontainers.NetworkContainers{}
dc, err := dockerclient.NewDefaultClient(wscli)
if err != nil {
return nil, err
}
res, err := wscli.GetInterfaces(context.TODO()) // TODO(rbtr): thread context through this client
if err != nil {
return nil, errors.Wrap(err, "failed to get interfaces from IMDS")
}
primaryInterface, err := wireserver.GetPrimaryInterfaceFromResult(res)
if err != nil {
return nil, errors.Wrap(err, "failed to get primary interface from IMDS response")
}
// add primaryInterfaceIP to cns config
config.Server.PrimaryInterfaceIP = primaryInterface.PrimaryIP
serviceState := &httpRestServiceState{
Networks: make(map[string]*networkInfo),
joinedNetworks: make(map[string]struct{}),
primaryInterface: primaryInterface,
PnpIDByMacAddress: make(map[string]string),
}
podIPIDByPodInterfaceKey := make(map[string][]string)
podIPConfigState := make(map[string]cns.IPConfigurationStatus)
if gen == nil {
gen = &NoOpConflistGenerator{}
}
return &HTTPRestService{
Service: service,
store: service.Service.Store,
dockerClient: dc,
wscli: wscli,
iptables: iptg,
nma: nmagentClient,
wsproxy: wsproxy,
networkContainer: nc,
PodIPIDByPodInterfaceKey: podIPIDByPodInterfaceKey,
PodIPConfigState: podIPConfigState,
routingTable: routingTable,
state: serviceState,
podsPendingIPAssignment: bounded.NewTimedSet(250), // nolint:gomnd // maxpods
EndpointStateStore: endpointStateStore,
EndpointState: make(map[string]*EndpointInfo),
homeAzMonitor: homeAzMonitor,
cniConflistGenerator: gen,
imdsClient: imdsClient,
}, nil
}
// Init starts the CNS listener.
func (service *HTTPRestService) Init(config *common.ServiceConfig) error {
err := service.Initialize(config)
if err != nil {
logger.Errorf("[Azure CNS] Failed to initialize base service, err:%v.", err)
return err
}
service.restoreState()
err = service.restoreNetworkState()
if err != nil {
logger.Errorf("[Azure CNS] Failed to restore network state, err:%v.", err)
return err
}
// Add handlers.
listener := service.Listener
// default handlers
listener.AddHandler(cns.SetEnvironmentPath, service.setEnvironment)
listener.AddHandler(cns.CreateNetworkPath, service.createNetwork)
listener.AddHandler(cns.DeleteNetworkPath, service.deleteNetwork)
listener.AddHandler(cns.GetHostLocalIPPath, service.getHostLocalIP)
listener.AddHandler(cns.CreateOrUpdateNetworkContainer, service.createOrUpdateNetworkContainer)
listener.AddHandler(cns.DeleteNetworkContainer, service.deleteNetworkContainer)
listener.AddHandler(cns.GetInterfaceForContainer, service.getInterfaceForContainer)
listener.AddHandler(cns.SetOrchestratorType, service.setOrchestratorType)
listener.AddHandler(cns.GetNetworkContainerByOrchestratorContext, service.GetNetworkContainerByOrchestratorContext)
listener.AddHandler(cns.GetAllNetworkContainers, service.GetAllNetworkContainers)
listener.AddHandler(cns.AttachContainerToNetwork, service.attachNetworkContainerToNetwork)
listener.AddHandler(cns.DetachContainerFromNetwork, service.detachNetworkContainerFromNetwork)
listener.AddHandler(cns.CreateHnsNetworkPath, service.createHnsNetwork)
listener.AddHandler(cns.DeleteHnsNetworkPath, service.deleteHnsNetwork)
listener.AddHandler(cns.NumberOfCPUCoresPath, service.getNumberOfCPUCores)
listener.AddHandler(cns.CreateHostNCApipaEndpointPath, service.CreateHostNCApipaEndpoint)
listener.AddHandler(cns.DeleteHostNCApipaEndpointPath, service.DeleteHostNCApipaEndpoint)
listener.AddHandler(cns.PublishNetworkContainer, service.publishNetworkContainer)
listener.AddHandler(cns.UnpublishNetworkContainer, service.unpublishNetworkContainer)
listener.AddHandler(cns.RequestIPConfig, NewHandlerFuncWithHistogram(service.RequestIPConfigHandler, HTTPRequestLatency))
listener.AddHandler(cns.RequestIPConfigs, NewHandlerFuncWithHistogram(service.RequestIPConfigsHandler, HTTPRequestLatency))
listener.AddHandler(cns.ReleaseIPConfig, NewHandlerFuncWithHistogram(service.ReleaseIPConfigHandler, HTTPRequestLatency))
listener.AddHandler(cns.ReleaseIPConfigs, NewHandlerFuncWithHistogram(service.ReleaseIPConfigsHandler, HTTPRequestLatency))
listener.AddHandler(cns.NmAgentSupportedApisPath, service.nmAgentSupportedApisHandler)
listener.AddHandler(cns.PathDebugIPAddresses, service.HandleDebugIPAddresses)
listener.AddHandler(cns.PathDebugPodContext, service.HandleDebugPodContext)
listener.AddHandler(cns.PathDebugRestData, service.HandleDebugRestData)
listener.AddHandler(cns.NetworkContainersURLPath, service.getOrRefreshNetworkContainers)
listener.AddHandler(cns.GetHomeAz, service.getHomeAz)
listener.AddHandler(cns.EndpointPath, service.EndpointHandlerAPI)
// This API is only needed for Direct channel mode.
if config.ChannelMode == cns.Direct {
listener.AddHandler(cns.GetVMUniqueID, service.getVMUniqueID)
listener.AddHandler(cns.GetNCList, service.nmAgentNCListHandler)
}
// handlers for v0.2
listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment)
listener.AddHandler(cns.V2Prefix+cns.CreateNetworkPath, service.createNetwork)
listener.AddHandler(cns.V2Prefix+cns.DeleteNetworkPath, service.deleteNetwork)
listener.AddHandler(cns.V2Prefix+cns.GetHostLocalIPPath, service.getHostLocalIP)
listener.AddHandler(cns.V2Prefix+cns.CreateOrUpdateNetworkContainer, service.createOrUpdateNetworkContainer)
listener.AddHandler(cns.V2Prefix+cns.DeleteNetworkContainer, service.deleteNetworkContainer)
listener.AddHandler(cns.V2Prefix+cns.GetInterfaceForContainer, service.getInterfaceForContainer)
listener.AddHandler(cns.V2Prefix+cns.SetOrchestratorType, service.setOrchestratorType)
listener.AddHandler(cns.V2Prefix+cns.GetNetworkContainerByOrchestratorContext, service.GetNetworkContainerByOrchestratorContext)
listener.AddHandler(cns.V2Prefix+cns.GetAllNetworkContainers, service.GetAllNetworkContainers)
listener.AddHandler(cns.V2Prefix+cns.AttachContainerToNetwork, service.attachNetworkContainerToNetwork)
listener.AddHandler(cns.V2Prefix+cns.DetachContainerFromNetwork, service.detachNetworkContainerFromNetwork)
listener.AddHandler(cns.V2Prefix+cns.CreateHnsNetworkPath, service.createHnsNetwork)
listener.AddHandler(cns.V2Prefix+cns.DeleteHnsNetworkPath, service.deleteHnsNetwork)
listener.AddHandler(cns.V2Prefix+cns.NumberOfCPUCoresPath, service.getNumberOfCPUCores)
listener.AddHandler(cns.V2Prefix+cns.CreateHostNCApipaEndpointPath, service.CreateHostNCApipaEndpoint)
listener.AddHandler(cns.V2Prefix+cns.DeleteHostNCApipaEndpointPath, service.DeleteHostNCApipaEndpoint)
listener.AddHandler(cns.V2Prefix+cns.NmAgentSupportedApisPath, service.nmAgentSupportedApisHandler)
listener.AddHandler(cns.V2Prefix+cns.GetHomeAz, service.getHomeAz)
listener.AddHandler(cns.V2Prefix+cns.EndpointPath, service.EndpointHandlerAPI)
// This API is only needed for Direct channel mode.
if config.ChannelMode == cns.Direct {
listener.AddHandler(cns.V2Prefix+cns.GetVMUniqueID, service.getVMUniqueID)
listener.AddHandler(cns.V2Prefix+cns.GetNCList, service.nmAgentNCListHandler)
}
// Initialize HTTP client to be reused in CNS
connectionTimeout, _ := service.GetOption(acn.OptHttpConnectionTimeout).(int)
responseHeaderTimeout, _ := service.GetOption(acn.OptHttpResponseHeaderTimeout).(int)
acn.InitHttpClient(connectionTimeout, responseHeaderTimeout)
logger.SetContextDetails(service.state.OrchestratorType, service.state.NodeID)
logger.Printf("[Azure CNS] Listening.")
return nil
}
func (service *HTTPRestService) RegisterPProfEndpoints() {
if service.Listener != nil {
mux := service.Listener.GetMux()
mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
}
// Start starts the CNS listener.
func (service *HTTPRestService) Start(config *common.ServiceConfig) error {
// Start the listener.
// continue to listen on the normal endpoint for http traffic, this will be supported
// for sometime until partners migrate fully to https
if err := service.StartListener(config); err != nil {
return err
}
return nil
}
// Stop stops the CNS.
func (service *HTTPRestService) Stop() {
service.Uninitialize()
logger.Printf("[Azure CNS] Service stopped.")
}
// MustGenerateCNIConflistOnce will generate the CNI conflist once if the service was initialized with
// a conflist generator. If not, this is a no-op.
func (service *HTTPRestService) MustGenerateCNIConflistOnce() {
service.generateCNIConflistOnce.Do(func() {
if err := service.cniConflistGenerator.Generate(); err != nil {
panic("unable to generate cni conflist with error: " + err.Error())
}
if err := service.cniConflistGenerator.Close(); err != nil {
panic("unable to close the cni conflist output stream: " + err.Error())
}
})
}
func (service *HTTPRestService) AttachIPConfigsHandlerMiddleware(middleware cns.IPConfigsHandlerMiddleware) {
service.IPConfigsHandlerMiddleware = middleware
}