-
Notifications
You must be signed in to change notification settings - Fork 260
Delegated Azure IPAM #1432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Delegated Azure IPAM #1432
Changes from all commits
ec90c11
ebb6a03
f9b89ba
a6986a5
75a3425
feb4951
924f842
197a99d
208da93
690951b
e0ff9f4
79c40e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "time" | ||
| ) | ||
|
|
||
| const ( | ||
| pluginName = "azure-ipam" | ||
| cnsBaseURL = "" // fallback to default http://localhost:10090 | ||
| cnsReqTimeout = 15 * time.Second | ||
| ) | ||
|
|
||
| // plugin specific error codes | ||
| // https://www.cni.dev/docs/spec/#error | ||
| const ( | ||
| ErrCreateIPConfigRequest uint = iota + 100 | ||
| ErrRequestIPConfigFromCNS | ||
| ErrProcessIPConfigResponse | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| module github.com/Azure/azure-container-networking/azure-ipam | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you try running a Actually, do we even need
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want it to be a separate module; I've been trying to move the functionally distinct binaries in this monorepo to modules for a while so that they can be released and versioned separately (it's not super clear from reading the mod proposal, but you can tag |
||
|
|
||
| go 1.18 | ||
|
|
||
| require ( | ||
| github.com/pkg/errors v0.9.1 | ||
| github.com/stretchr/testify v1.7.1 | ||
| go.uber.org/zap v1.21.0 | ||
| ) | ||
|
|
||
| require ( | ||
| code.cloudfoundry.org/clock v1.0.0 // indirect | ||
| github.com/Masterminds/semver v1.5.0 // indirect | ||
| github.com/Microsoft/go-winio v0.4.17 // indirect | ||
| github.com/Microsoft/hcsshim v0.8.23 // indirect | ||
| github.com/PuerkitoBio/purell v1.1.1 // indirect | ||
| github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect | ||
| github.com/beorn7/perks v1.0.1 // indirect | ||
| github.com/billgraziano/dpapi v0.4.0 // indirect | ||
| github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||
| github.com/containerd/cgroups v1.0.1 // indirect | ||
| github.com/davecgh/go-spew v1.1.1 // indirect | ||
| github.com/emicklei/go-restful v2.9.5+incompatible // indirect | ||
| github.com/go-logr/logr v1.2.0 // indirect | ||
| github.com/go-openapi/jsonpointer v0.19.5 // indirect | ||
| github.com/go-openapi/jsonreference v0.19.5 // indirect | ||
| github.com/go-openapi/swag v0.19.14 // indirect | ||
| github.com/gofrs/uuid v4.2.0+incompatible // indirect | ||
| github.com/gogo/protobuf v1.3.2 // indirect | ||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||
| github.com/golang/protobuf v1.5.2 // indirect | ||
| github.com/google/gnostic v0.5.7-v3refs // indirect | ||
| github.com/google/go-cmp v0.5.8 // indirect | ||
| github.com/google/gofuzz v1.2.0 // indirect | ||
| github.com/google/uuid v1.3.0 // indirect | ||
| github.com/imdario/mergo v0.3.12 // indirect | ||
| github.com/josharian/intern v1.0.0 // indirect | ||
| github.com/json-iterator/go v1.1.12 // indirect | ||
| github.com/mailru/easyjson v0.7.6 // indirect | ||
| github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect | ||
| github.com/microsoft/ApplicationInsights-Go v0.4.4 // indirect | ||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
| github.com/modern-go/reflect2 v1.0.2 // indirect | ||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||
| github.com/pmezard/go-difflib v1.0.0 // indirect | ||
| github.com/prometheus/client_golang v1.12.2 // indirect | ||
| github.com/prometheus/client_model v0.2.0 // indirect | ||
| github.com/prometheus/common v0.32.1 // indirect | ||
| github.com/prometheus/procfs v0.7.3 // indirect | ||
| github.com/sirupsen/logrus v1.8.1 // indirect | ||
| github.com/spf13/pflag v1.0.5 // indirect | ||
| go.opencensus.io v0.23.0 // indirect | ||
| golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect | ||
| golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect | ||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect | ||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect | ||
| golang.org/x/text v0.3.7 // indirect | ||
| golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect | ||
| google.golang.org/appengine v1.6.7 // indirect | ||
| google.golang.org/protobuf v1.28.0 // indirect | ||
| gopkg.in/inf.v0 v0.9.1 // indirect | ||
| gopkg.in/yaml.v2 v2.4.0 // indirect | ||
| gopkg.in/yaml.v3 v3.0.0 // indirect | ||
| k8s.io/api v0.24.1 // indirect | ||
| k8s.io/apimachinery v0.24.1 // indirect | ||
| k8s.io/client-go v0.24.1 // indirect | ||
| k8s.io/klog/v2 v2.60.1 // indirect | ||
| k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect | ||
| k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect | ||
| sigs.k8s.io/controller-runtime v0.12.1 // indirect | ||
| sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect | ||
| sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect | ||
| sigs.k8s.io/yaml v1.3.0 // indirect | ||
| ) | ||
|
|
||
| require ( | ||
| github.com/Azure/azure-container-networking v1.4.27 | ||
| github.com/containernetworking/cni v1.1.1 | ||
| github.com/containernetworking/plugins v1.1.1 | ||
| go.uber.org/atomic v1.9.0 // indirect | ||
| go.uber.org/multierr v1.8.0 // indirect | ||
| ) | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package buildinfo | ||
|
|
||
| // this will be populate by the Go compiler via | ||
| // the -ldflags, which insert dynamic information | ||
| // into the binary at build time | ||
| var Version string | ||
nddq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "io" | ||
| "net" | ||
|
|
||
| "github.com/Azure/azure-container-networking/azure-ipam/internal/buildinfo" | ||
| "github.com/Azure/azure-container-networking/azure-ipam/ipconfig" | ||
| "github.com/Azure/azure-container-networking/cns" | ||
| cniSkel "github.com/containernetworking/cni/pkg/skel" | ||
| cniTypes "github.com/containernetworking/cni/pkg/types" | ||
| types100 "github.com/containernetworking/cni/pkg/types/100" | ||
| "github.com/pkg/errors" | ||
| "go.uber.org/zap" | ||
| ) | ||
|
|
||
| // IPAMPlugin is the struct for the delegated azure-ipam plugin | ||
nddq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // https://www.cni.dev/docs/spec/#section-4-plugin-delegation | ||
| type IPAMPlugin struct { | ||
| Name string | ||
| Version string | ||
| Options map[string]interface{} | ||
| logger *zap.Logger | ||
| cnsClient cnsClient | ||
| out io.Writer // indicate the output channel for the plugin | ||
| } | ||
|
|
||
| type cnsClient interface { | ||
nddq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| RequestIPAddress(context.Context, cns.IPConfigRequest) (*cns.IPConfigResponse, error) | ||
| ReleaseIPAddress(context.Context, cns.IPConfigRequest) error | ||
| } | ||
|
|
||
| // NewPlugin constructs a new IPAM plugin instance with given logger and CNS client | ||
| func NewPlugin(logger *zap.Logger, c cnsClient, out io.Writer) (*IPAMPlugin, error) { | ||
| plugin := &IPAMPlugin{ | ||
| Name: pluginName, | ||
| Version: buildinfo.Version, | ||
| logger: logger, | ||
| out: out, | ||
| cnsClient: c, | ||
| } | ||
| return plugin, nil | ||
| } | ||
|
|
||
| // | ||
| // CNI implementation | ||
| // https://github.com/containernetworking/cni/blob/master/SPEC.md | ||
| // | ||
|
|
||
| // CmdAdd handles CNI add commands. | ||
| func (p *IPAMPlugin) CmdAdd(args *cniSkel.CmdArgs) error { | ||
| p.logger.Info("ADD called", zap.Any("args", args)) | ||
|
|
||
| // Parsing network conf | ||
| nwCfg, err := parseNetConf(args.StdinData) | ||
| if err != nil { | ||
| p.logger.Error("Failed to parse CNI network config from stdin", zap.Error(err), zap.Any("argStdinData", args.StdinData)) | ||
| return cniTypes.NewError(cniTypes.ErrDecodingFailure, err.Error(), "failed to parse CNI network config from stdin") | ||
| } | ||
| p.logger.Debug("Parsed network config", zap.Any("netconf", nwCfg)) | ||
|
|
||
| // Create ip config request from args | ||
| req, err := ipconfig.CreateIPConfigReq(args) | ||
| if err != nil { | ||
| p.logger.Error("Failed to create CNS IP config request", zap.Error(err)) | ||
| return cniTypes.NewError(ErrCreateIPConfigRequest, err.Error(), "failed to create CNS IP config request") | ||
| } | ||
| p.logger.Debug("Created CNS IP config request", zap.Any("request", req)) | ||
|
|
||
| p.logger.Debug("Making request to CNS") | ||
| // if this fails, the caller plugin should execute again with cmdDel before returning error. | ||
| // https://www.cni.dev/docs/spec/#delegated-plugin-execution-procedure | ||
| resp, err := p.cnsClient.RequestIPAddress(context.TODO(), req) | ||
| if err != nil { | ||
| p.logger.Error("Failed to request IP address from CNS", zap.Error(err), zap.Any("request", req)) | ||
| return cniTypes.NewError(ErrRequestIPConfigFromCNS, err.Error(), "failed to request IP address from CNS") | ||
| } | ||
| p.logger.Debug("Received CNS IP config response", zap.Any("response", resp)) | ||
|
|
||
| // Get Pod IP and gateway IP from ip config response | ||
| podIPNet, gwIP, err := ipconfig.ProcessIPConfigResp(resp) | ||
| if err != nil { | ||
| p.logger.Error("Failed to interpret CNS IPConfigResponse", zap.Error(err), zap.Any("response", resp)) | ||
| return cniTypes.NewError(ErrProcessIPConfigResponse, err.Error(), "failed to interpret CNS IPConfigResponse") | ||
| } | ||
| p.logger.Debug("Parsed pod IP and gateway IP", zap.String("podIPNet", podIPNet.String()), zap.String("gwIP", gwIP.String())) | ||
|
|
||
| cniResult := &types100.Result{ | ||
| IPs: []*types100.IPConfig{ | ||
| { | ||
| Address: net.IPNet{ | ||
| IP: net.ParseIP(podIPNet.Addr().String()), | ||
| Mask: net.CIDRMask(podIPNet.Bits(), 32), // nolint | ||
| }, | ||
| Gateway: net.ParseIP(gwIP.String()), | ||
| }, | ||
| }, | ||
| Routes: []*cniTypes.Route{ | ||
tamilmani1989 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Dst: net.IPNet{ | ||
| IP: net.IPv4zero, | ||
| Mask: net.IPv4Mask(0, 0, 0, 0), | ||
| }, | ||
| GW: net.ParseIP(gwIP.String()), | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| // Get versioned result | ||
| versionedCniResult, err := cniResult.GetAsVersion(nwCfg.CNIVersion) | ||
| if err != nil { | ||
| p.logger.Error("Failed to interpret CNI result with netconf CNI version", zap.Error(err), zap.Any("cniVersion", nwCfg.CNIVersion)) | ||
| return cniTypes.NewError(cniTypes.ErrIncompatibleCNIVersion, err.Error(), "failed to interpret CNI result with netconf CNI version") | ||
| } | ||
|
|
||
| p.logger.Info("ADD success", zap.Any("result", versionedCniResult)) | ||
|
|
||
| // Write result to output channel | ||
| err = versionedCniResult.PrintTo(p.out) | ||
| if err != nil { | ||
| p.logger.Error("Failed to print CNI result to output channel", zap.Error(err), zap.Any("result", versionedCniResult)) | ||
| return cniTypes.NewError(cniTypes.ErrIOFailure, err.Error(), "failed to print CNI result to output channel") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // CmdDel handles CNI delete commands. | ||
| func (p *IPAMPlugin) CmdDel(args *cniSkel.CmdArgs) error { | ||
| p.logger.Info("DEL called", zap.Any("args", args)) | ||
|
|
||
| // Create ip config request from args | ||
| req, err := ipconfig.CreateIPConfigReq(args) | ||
| if err != nil { | ||
| p.logger.Error("Failed to create CNS IP config request", zap.Error(err)) | ||
| return cniTypes.NewError(cniTypes.ErrTryAgainLater, err.Error(), "failed to create CNS IP config request") | ||
| } | ||
| p.logger.Debug("Created CNS IP config request", zap.Any("request", req)) | ||
|
|
||
| p.logger.Debug("Making request to CNS") | ||
| // cnsClient enforces it own timeout | ||
| if err := p.cnsClient.ReleaseIPAddress(context.TODO(), req); err != nil { | ||
| p.logger.Error("Failed to release IP address from CNS", zap.Error(err), zap.Any("request", req)) | ||
| return cniTypes.NewError(cniTypes.ErrTryAgainLater, err.Error(), "failed to release IP address from CNS") | ||
| } | ||
|
|
||
nddq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| p.logger.Info("DEL success") | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // CmdCheck handles CNI check command - not implemented | ||
| func (p *IPAMPlugin) CmdCheck(args *cniSkel.CmdArgs) error { | ||
| p.logger.Info("CHECK called") | ||
| return nil | ||
| } | ||
|
|
||
| // Parse network config from given byte array | ||
| func parseNetConf(b []byte) (*cniTypes.NetConf, error) { | ||
| netConf := &cniTypes.NetConf{} | ||
| err := json.Unmarshal(b, netConf) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to unmarshal net conf") | ||
| } | ||
|
|
||
| if netConf.CNIVersion == "" { | ||
| netConf.CNIVersion = "0.2.0" // default CNI version | ||
| } | ||
|
|
||
| return netConf, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.