azure-container-networking/nmagent/requests.go

565 строки
16 KiB
Go

package nmagent
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"unicode"
"github.com/Azure/azure-container-networking/nmagent/internal"
"github.com/pkg/errors"
)
// Request represents an abstracted HTTP request, capable of validating itself,
// producing a valid Path, Body, and its Method.
type Request interface {
// Validate should ensure that the request is valid to submit
Validate() error
// Path should produce a URL path, complete with any URL parameters
// interpolated
Path() string
// Body produces the HTTP request body necessary to submit the request
Body() (io.Reader, error)
// Method returns the HTTP Method to be used for the request.
Method() string
}
var _ Request = &PutNetworkContainerRequest{}
// PutNetworkContainerRequest is a collection of parameters necessary to create
// a new network container
type PutNetworkContainerRequest struct {
// NOTE(traymond): if you are adding a new field to this struct, ensure that it is also added
// to the MarshallJSON, UnmarshallJSON and method as well.
ID string // the id of the network container
VNetID string // the id of the customer's vnet
// Version is the new network container version
Version uint64
// SubnetName is the name of the delegated subnet. This is used to
// authenticate the request. The list of ipv4addresses must be contained in
// the subnet's prefix.
SubnetName string
// IPv4 addresses in the customer virtual network that will be assigned to
// the interface.
IPv4Addrs []string
Policies []Policy // policies applied to the network container
// VlanID is used to distinguish Network Containers with duplicate customer
// addresses. "0" is considered a default value by the API.
VlanID int
GREKey uint16
// AuthenticationToken is the base64 security token for the subnet containing
// the Network Container addresses
AuthenticationToken string
// PrimaryAddress is the primary customer address of the interface in the
// management VNet
PrimaryAddress string
// AzID is the home AZ ID of the network container
AzID uint
// AZREnabled denotes whether AZR is enabled for network container or not
AZREnabled bool
}
type internalNC struct {
// NMAgent expects this to be a string, except that the contents of that string have to be a uint64.
// Therefore, the type we expose to clients uses a uint64 to guarantee that, but we
// convert it to a string here.
Version string `json:"version"`
// The rest of these are copied verbatim from the above struct and should be kept in sync.
VNetID string `json:"virtualNetworkId"`
SubnetName string `json:"subnetName"`
IPv4Addrs []string `json:"ipV4Addresses"`
Policies []Policy `json:"policies"`
VlanID int `json:"vlanId"`
GREKey uint16 `json:"greKey"`
AzID uint `json:"azID"`
AZREnabled bool `json:"azrEnabled"`
}
func (p *PutNetworkContainerRequest) MarshalJSON() ([]byte, error) {
pBody := internalNC{
Version: strconv.Itoa(int(p.Version)),
VNetID: p.VNetID,
SubnetName: p.SubnetName,
IPv4Addrs: p.IPv4Addrs,
Policies: p.Policies,
VlanID: p.VlanID,
GREKey: p.GREKey,
AzID: p.AzID,
AZREnabled: p.AZREnabled,
}
body, err := json.Marshal(pBody)
if err != nil {
return nil, errors.Wrap(err, "marshaling PutNetworkContainerRequest")
}
return body, nil
}
func (p *PutNetworkContainerRequest) UnmarshalJSON(in []byte) error {
var req internalNC
err := json.Unmarshal(in, &req)
if err != nil {
return errors.Wrap(err, "unmarshal network container request")
}
//nolint:gomnd // these magic numbers are well-documented in ParseUint
version, err := strconv.ParseUint(req.Version, 10, 64)
if err != nil {
return errors.Wrap(err, "parsing version string as uint64")
}
p.Version = version
p.VNetID = req.VNetID
p.SubnetName = req.SubnetName
p.IPv4Addrs = req.IPv4Addrs
p.Policies = req.Policies
p.VlanID = req.VlanID
p.GREKey = req.GREKey
p.AzID = req.AzID
p.AZREnabled = req.AZREnabled
return nil
}
// Body marshals the JSON fields of the request and produces an Reader intended
// for use with an HTTP request
func (p *PutNetworkContainerRequest) Body() (io.Reader, error) {
body, err := json.Marshal(p)
if err != nil {
return nil, errors.Wrap(err, "marshaling PutNetworkContainerRequest")
}
return bytes.NewReader(body), nil
}
// Method returns the HTTP method for this request type
func (p *PutNetworkContainerRequest) Method() string {
return http.MethodPost
}
// Path returns the URL path necessary to submit this PutNetworkContainerRequest
func (p *PutNetworkContainerRequest) Path() string {
const PutNCRequestPath string = "/NetworkManagement/interfaces/%s/networkContainers/%s/authenticationToken/%s/api-version/1"
return fmt.Sprintf(PutNCRequestPath, p.PrimaryAddress, p.ID, p.AuthenticationToken)
}
// Validate ensures that all of the required parameters of the request have
// been filled out properly prior to submission to NMAgent
func (p *PutNetworkContainerRequest) Validate() error {
err := internal.ValidationError{}
// URL requirements:
if p.PrimaryAddress == "" {
err.MissingFields = append(err.MissingFields, "PrimaryAddress")
}
if p.ID == "" {
err.MissingFields = append(err.MissingFields, "ID")
}
if p.AuthenticationToken == "" {
err.MissingFields = append(err.MissingFields, "AuthenticationToken")
}
// Documented requirements:
if p.SubnetName == "" {
err.MissingFields = append(err.MissingFields, "SubnetName")
}
if len(p.IPv4Addrs) == 0 {
err.MissingFields = append(err.MissingFields, "IPv4Addrs")
}
if p.VNetID == "" {
err.MissingFields = append(err.MissingFields, "VNetID")
}
if err.IsEmpty() {
return nil
}
return err
}
type Policy struct {
ID string
Type string
}
// MarshalJson encodes policies as a JSON string, separated by a comma. This
// specific format is requested by the NMAgent documentation
func (p Policy) MarshalJSON() ([]byte, error) {
out := bytes.NewBufferString(p.ID)
out.WriteString(", ")
out.WriteString(p.Type)
outStr := out.String()
// nolint:wrapcheck // wrapping this error provides no useful information
return json.Marshal(outStr)
}
// UnmarshalJSON decodes a JSON-encoded policy string
func (p *Policy) UnmarshalJSON(in []byte) error {
const expectedNumParts = 2
var raw string
err := json.Unmarshal(in, &raw)
if err != nil {
return errors.Wrap(err, "decoding policy")
}
parts := strings.Split(raw, ",")
if len(parts) != expectedNumParts {
return errors.New("policies must be two comma-separated values")
}
p.ID = strings.TrimFunc(parts[0], unicode.IsSpace)
p.Type = strings.TrimFunc(parts[1], unicode.IsSpace)
return nil
}
var _ Request = JoinNetworkRequest{}
type JoinNetworkRequest struct {
NetworkID string `json:"-"` // the customer's VNet ID
}
// Path constructs a URL path for invoking a JoinNetworkRequest using the
// provided parameters
func (j JoinNetworkRequest) Path() string {
const JoinNetworkPath string = "/NetworkManagement/joinedVirtualNetworks/%s/api-version/1"
return fmt.Sprintf(JoinNetworkPath, j.NetworkID)
}
// Body returns nothing, because JoinNetworkRequest has no request body
func (j JoinNetworkRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method returns the HTTP request method to submit a JoinNetworkRequest
func (j JoinNetworkRequest) Method() string {
return http.MethodPost
}
// Validate ensures that the provided parameters of the request are valid
func (j JoinNetworkRequest) Validate() error {
err := internal.ValidationError{}
if j.NetworkID == "" {
err.MissingFields = append(err.MissingFields, "NetworkID")
}
if err.IsEmpty() {
return nil
}
return err
}
var _ Request = DeleteNetworkRequest{}
// DeleteNetworkRequest represents all information necessary to request that
// NMAgent delete a particular network
type DeleteNetworkRequest struct {
NetworkID string `json:"-"` // the customer's VNet ID
}
// Path constructs a URL path for invoking a DeleteNetworkRequest using the
// provided parameters
func (d DeleteNetworkRequest) Path() string {
const DeleteNetworkPath = "/NetworkManagement/joinedVirtualNetworks/%s/api-version/1/method/DELETE"
return fmt.Sprintf(DeleteNetworkPath, d.NetworkID)
}
// Body returns nothing, because DeleteNetworkRequest has no request body
func (d DeleteNetworkRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method returns the HTTP request method to submit a DeleteNetworkRequest
func (d DeleteNetworkRequest) Method() string {
return http.MethodPost
}
// Validate ensures that the provided parameters of the request are valid
func (d DeleteNetworkRequest) Validate() error {
err := internal.ValidationError{}
if d.NetworkID == "" {
err.MissingFields = append(err.MissingFields, "NetworkID")
}
if err.IsEmpty() {
return nil
}
return err
}
var _ Request = DeleteContainerRequest{}
// DeleteContainerRequest represents all information necessary to request that
// NMAgent delete a particular network container
type DeleteContainerRequest struct {
NCID string `json:"-"` // the Network Container ID
AzID uint `json:"azID"` // home AZ of the Network Container
AZREnabled bool `json:"azrEnabled"` // whether AZR is enabled or not
// PrimaryAddress is the primary customer address of the interface in the
// management VNET
PrimaryAddress string `json:"-"`
AuthenticationToken string `json:"-"`
}
// Path returns the path for submitting a DeleteContainerRequest with
// parameters interpolated correctly
func (d DeleteContainerRequest) Path() string {
const DeleteNCPath string = "/NetworkManagement/interfaces/%s/networkContainers/%s/authenticationToken/%s/api-version/1/method/DELETE"
return fmt.Sprintf(DeleteNCPath, d.PrimaryAddress, d.NCID, d.AuthenticationToken)
}
// Body returns nothing, because DeleteContainerRequests have no HTTP body
func (d DeleteContainerRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method returns the HTTP method required to submit a DeleteContainerRequest
func (d DeleteContainerRequest) Method() string {
return http.MethodPost
}
// Validate ensures that the DeleteContainerRequest has the correct information
// to submit the request
func (d DeleteContainerRequest) Validate() error {
err := internal.ValidationError{}
if d.NCID == "" {
err.MissingFields = append(err.MissingFields, "NCID")
}
if d.PrimaryAddress == "" {
err.MissingFields = append(err.MissingFields, "PrimaryAddress")
}
if d.AuthenticationToken == "" {
err.MissingFields = append(err.MissingFields, "AuthenticationToken")
}
if err.IsEmpty() {
return nil
}
return err
}
var _ Request = GetNetworkConfigRequest{}
// GetNetworkConfigRequest is a collection of necessary information for
// submitting a request for a customer's network configuration
type GetNetworkConfigRequest struct {
VNetID string `json:"-"` // the customer's virtual network ID
}
// Path produces a URL path used to submit a request
func (g GetNetworkConfigRequest) Path() string {
const GetNetworkConfigPath string = "/NetworkManagement/joinedVirtualNetworks/%s/api-version/1"
return fmt.Sprintf(GetNetworkConfigPath, g.VNetID)
}
// Body returns nothing because GetNetworkConfigRequest has no HTTP request
// body
func (g GetNetworkConfigRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method returns the HTTP method required to submit a GetNetworkConfigRequest
func (g GetNetworkConfigRequest) Method() string {
return http.MethodGet
}
// Validate ensures that the request is complete and the parameters are correct
func (g GetNetworkConfigRequest) Validate() error {
err := internal.ValidationError{}
if g.VNetID == "" {
err.MissingFields = append(err.MissingFields, "VNetID")
}
if err.IsEmpty() {
return nil
}
return err
}
var _ Request = &SupportedAPIsRequest{}
// SupportedAPIsRequest is a collection of parameters necessary to submit a
// valid request to retrieve the supported APIs from an NMAgent instance.
type SupportedAPIsRequest struct{}
// Body is a no-op method to satisfy the Request interface while indicating
// that there is no body for a SupportedAPIs Request.
func (s *SupportedAPIsRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method indicates that SupportedAPIs requests are GET requests.
func (s *SupportedAPIsRequest) Method() string {
return http.MethodGet
}
// Path returns the necessary URI path for invoking a supported APIs request.
func (s *SupportedAPIsRequest) Path() string {
return "/GetSupportedApis"
}
// Validate is a no-op method because SupportedAPIsRequests have no parameters,
// and therefore can never be invalid.
func (s *SupportedAPIsRequest) Validate() error {
return nil
}
var _ Request = NCVersionRequest{}
type NCVersionRequest struct {
AuthToken string `json:"-"`
NetworkContainerID string `json:"-"`
PrimaryAddress string `json:"-"`
}
func (n NCVersionRequest) Body() (io.Reader, error) {
// there is no body to an NCVersionRequest, so return nil
return nil, nil
}
// Method indicates this request is a GET request
func (n NCVersionRequest) Method() string {
return http.MethodGet
}
// Path returns the URL Path for the request with parameters interpolated as
// necessary.
func (n NCVersionRequest) Path() string {
const path = "/NetworkManagement/interfaces/%s/networkContainers/%s/version/authenticationToken/%s/api-version/1"
return fmt.Sprintf(path, n.PrimaryAddress, n.NetworkContainerID, n.AuthToken)
}
// Validate ensures the presence of all parameters of the NCVersionRequest, as
// none are optional.
func (n NCVersionRequest) Validate() error {
err := internal.ValidationError{}
if n.AuthToken == "" {
err.MissingFields = append(err.MissingFields, "AuthToken")
}
if n.NetworkContainerID == "" {
err.MissingFields = append(err.MissingFields, "NetworkContainerID")
}
if n.PrimaryAddress == "" {
err.MissingFields = append(err.MissingFields, "PrimaryAddress")
}
if err.IsEmpty() {
return nil
}
return err
}
var _ Request = NCVersionListRequest{}
// NCVersionListRequest is a collection of parameters necessary to submit a
// request to receive a list of NCVersions available from the NMAgent instance.
type NCVersionListRequest struct{}
func (NCVersionListRequest) Body() (io.Reader, error) {
// there is no body for this request so...
return nil, nil
}
// Method returns the HTTP method required for the request.
func (NCVersionListRequest) Method() string {
return http.MethodGet
}
// Path returns the path required to issue the request.
func (NCVersionListRequest) Path() string {
return "/NetworkManagement/interfaces/api-version/2"
}
// Validate performs any necessary validations for the request.
func (NCVersionListRequest) Validate() error {
// there are no parameters, thus nothing to validate. Since the request
// cannot be made invalid it's fine for this to simply...
return nil
}
var _ Request = &GetHomeAzRequest{}
type GetHomeAzRequest struct{}
// Body is a no-op method to satisfy the Request interface while indicating
// that there is no body for a GetHomeAz Request.
func (g *GetHomeAzRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method indicates that GetHomeAz requests are GET requests.
func (g *GetHomeAzRequest) Method() string {
return http.MethodGet
}
// Path returns the necessary URI path for invoking a GetHomeAz request.
func (g *GetHomeAzRequest) Path() string {
return "/GetHomeAz/api-version/1"
}
// Validate is a no-op method because GetHomeAzRequest have no parameters,
// and therefore can never be invalid.
func (g *GetHomeAzRequest) Validate() error {
return nil
}
var _ Request = &GetSecondaryIPsRequest{}
type GetSecondaryIPsRequest struct{}
// Body is a no-op method to satisfy the Request interface while indicating
// that there is no body for a GetSecondaryIPsRequest Request.
func (g *GetSecondaryIPsRequest) Body() (io.Reader, error) {
return nil, nil
}
// Method indicates that GetSecondaryIPsRequest requests are GET requests.
func (g *GetSecondaryIPsRequest) Method() string {
return http.MethodGet
}
// Path returns the necessary URI path for invoking a GetSecondaryIPsRequest request.
func (g *GetSecondaryIPsRequest) Path() string {
return "getinterfaceinfov1"
}
// Validate is a no-op method because parameters are hard coded in the path,
// no customization needed.
func (g *GetSecondaryIPsRequest) Validate() error {
return nil
}