diff --git a/cns/api.go b/cns/api.go index 6c37f0cc96..09f138948e 100644 --- a/cns/api.go +++ b/cns/api.go @@ -3,11 +3,15 @@ package cns +import "encoding/json" + // Container Network Service remote API Contract const ( SetEnvironmentPath = "/network/environment" CreateNetworkPath = "/network/create" DeleteNetworkPath = "/network/delete" + CreateHnsNetworkPath = "/network/hns/create" + DeleteHnsNetworkPath = "/network/hns/delete" ReserveIPAddressPath = "/network/ip/reserve" ReleaseIPAddressPath = "/network/ip/release" GetHostLocalIPPath = "/network/ip/hostlocal" @@ -44,6 +48,42 @@ type DeleteNetworkRequest struct { NetworkName string } +// CreateHnsNetworkRequest describes request to create the HNS network. +type CreateHnsNetworkRequest struct { + NetworkName string + NetworkType string + NetworkAdapterName string `json:",omitempty"` + SourceMac string `json:",omitempty"` + Policies []json.RawMessage `json:",omitempty"` + MacPools []MacPool `json:",omitempty"` + Subnets []SubnetInfo + DNSSuffix string `json:",omitempty"` + DNSServerList string `json:",omitempty"` + DNSServerCompartment uint32 `json:",omitempty"` + ManagementIP string `json:",omitempty"` + AutomaticDNS bool `json:",omitempty"` +} + +// SubnetInfo is assoicated with HNS network and represents a list +// of subnets available to the network +type SubnetInfo struct { + AddressPrefix string `json:",omitempty"` + GatewayAddress string `json:",omitempty"` + Policies []json.RawMessage `json:",omitempty"` +} + +// MacPool is assoicated with HNS network and represents a list +// of macaddresses available to the network +type MacPool struct { + StartMacAddress string `json:",omitempty"` + EndMacAddress string `json:",omitempty"` +} + +// DeleteHnsNetworkRequest describes request to delete the HNS network. +type DeleteHnsNetworkRequest struct { + NetworkName string +} + // ReserveIPAddressRequest describes request to reserve an IP Address type ReserveIPAddressRequest struct { ReservationID string diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 0de0b77e15..825c746ef9 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -154,6 +154,8 @@ func (service *HTTPRestService) Start(config *common.ServiceConfig) error { listener.AddHandler(cns.GetNetworkContainerByOrchestratorContext, service.getNetworkContainerByOrchestratorContext) listener.AddHandler(cns.AttachContainerToNetwork, service.attachNetworkContainerToNetwork) listener.AddHandler(cns.DetachContainerFromNetwork, service.detachNetworkContainerFromNetwork) + listener.AddHandler(cns.CreateHnsNetworkPath, service.createHnsNetwork) + listener.AddHandler(cns.DeleteHnsNetworkPath, service.deleteHnsNetwork) // handlers for v0.2 listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment) @@ -172,6 +174,8 @@ func (service *HTTPRestService) Start(config *common.ServiceConfig) error { listener.AddHandler(cns.V2Prefix+cns.GetNetworkContainerByOrchestratorContext, service.getNetworkContainerByOrchestratorContext) 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) log.Printf("[Azure CNS] Listening.") return nil @@ -248,7 +252,7 @@ func (service *HTTPRestService) createNetwork(w http.ResponseWriter, r *http.Req case "Underlay": switch service.state.Location { case "Azure": - log.Printf("[Azure CNS] Goign to create network with name %v.", req.NetworkName) + log.Printf("[Azure CNS] Creating network with name %v.", req.NetworkName) err = rt.GetRoutingTable() if err != nil { @@ -344,7 +348,7 @@ func (service *HTTPRestService) deleteNetwork(w http.ResponseWriter, r *http.Req // Network does exist if err == nil { - log.Printf("[Azure CNS] Goign to delete network with name %v.", req.NetworkName) + log.Printf("[Azure CNS] Deleting network with name %v.", req.NetworkName) err := dc.DeleteNetwork(req.NetworkName) if err != nil { returnMessage = fmt.Sprintf("[Azure CNS] Error. DeleteNetwork failed %v.", err.Error()) @@ -379,6 +383,115 @@ func (service *HTTPRestService) deleteNetwork(w http.ResponseWriter, r *http.Req log.Response(service.Name, resp, resp.ReturnCode, ReturnCodeToString(resp.ReturnCode), err) } +// Handles CreateHnsNetwork requests. +func (service *HTTPRestService) createHnsNetwork(w http.ResponseWriter, r *http.Request) { + log.Printf("[Azure CNS] createHnsNetwork") + + var err error + returnCode := 0 + returnMessage := "" + + var req cns.CreateHnsNetworkRequest + err = service.Listener.Decode(w, r, &req) + log.Request(service.Name, &req, err) + + if err != nil { + returnMessage = fmt.Sprintf("[Azure CNS] Error. Unable to decode input request.") + returnCode = InvalidParameter + } else { + switch r.Method { + case "POST": + if err := platform.CreateHnsNetwork(req); err == nil { + // Save the newly created HnsNetwork name. CNS deleteHnsNetwork API + // will only allow deleting these networks. + networkInfo := &networkInfo{ + NetworkName: req.NetworkName, + } + service.lock.Lock() + service.state.Networks[req.NetworkName] = networkInfo + service.lock.Unlock() + returnMessage = fmt.Sprintf("[Azure CNS] Successfully created HNS network: %s", req.NetworkName) + } else { + returnMessage = fmt.Sprintf("[Azure CNS] CreateHnsNetwork failed with error %v", err.Error()) + returnCode = UnexpectedError + } + default: + returnMessage = "[Azure CNS] Error. CreateHnsNetwork did not receive a POST." + returnCode = InvalidParameter + } + } + + resp := &cns.Response{ + ReturnCode: returnCode, + Message: returnMessage, + } + + err = service.Listener.Encode(w, &resp) + + if returnCode == 0 { + service.lock.Lock() + service.saveState() + service.lock.Unlock() + } + + log.Response(service.Name, resp, resp.ReturnCode, ReturnCodeToString(resp.ReturnCode), err) +} + +// Handles deleteHnsNetwork requests. +func (service *HTTPRestService) deleteHnsNetwork(w http.ResponseWriter, r *http.Request) { + log.Printf("[Azure CNS] deleteHnsNetwork") + + var err error + var req cns.DeleteHnsNetworkRequest + returnCode := 0 + returnMessage := "" + + err = service.Listener.Decode(w, r, &req) + log.Request(service.Name, &req, err) + + if err != nil { + returnMessage = fmt.Sprintf("[Azure CNS] Error. Unable to decode input request.") + returnCode = InvalidParameter + } else { + switch r.Method { + case "POST": + service.lock.Lock() + networkInfo, ok := service.state.Networks[req.NetworkName] + if ok && networkInfo.NetworkName == req.NetworkName { + if err = platform.DeleteHnsNetwork(req.NetworkName); err == nil { + returnMessage = fmt.Sprintf("[Azure CNS] Successfully deleted HNS network: %s", req.NetworkName) + } else { + returnMessage = fmt.Sprintf("[Azure CNS] DeleteHnsNetwork failed with error %v", err.Error()) + returnCode = UnexpectedError + } + } else { + returnMessage = fmt.Sprintf("[Azure CNS] Network %s not found", req.NetworkName) + returnCode = InvalidParameter + } + service.lock.Unlock() + default: + returnMessage = "[Azure CNS] Error. DeleteHnsNetwork did not receive a POST." + returnCode = InvalidParameter + } + } + + resp := &cns.Response{ + ReturnCode: returnCode, + Message: returnMessage, + } + + err = service.Listener.Encode(w, &resp) + + if returnCode == 0 { + service.lock.Lock() + delete(service.state.Networks, req.NetworkName) + service.saveState() + service.lock.Unlock() + } + + log.Response(service.Name, resp, resp.ReturnCode, ReturnCodeToString(resp.ReturnCode), err) +} + // Handles ip reservation requests. func (service *HTTPRestService) reserveIPAddress(w http.ResponseWriter, r *http.Request) { log.Printf("[Azure CNS] reserveIPAddress") diff --git a/cns/service/main.go b/cns/service/main.go index 57e6368633..c72daae114 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/signal" + "strings" "syscall" "github.com/Azure/azure-container-networking/cnm/ipam" @@ -136,6 +137,13 @@ var args = acn.ArgumentList{ Type: "string", DefaultValue: platform.K8SNetConfigPath + string(os.PathSeparator) + defaultCNINetworkConfigFileName, }, + { + Name: acn.OptCreateDefaultExtNetworkType, + Shorthand: acn.OptCreateDefaultExtNetworkTypeAlias, + Description: "Create default external network for windows platform with the specified type (l2bridge or l2tunnel)", + Type: "string", + DefaultValue: "", + }, { Name: acn.OptTelemetry, Shorthand: acn.OptTelemetryAlias, @@ -169,6 +177,7 @@ func main() { ipamQueryInterval, _ := acn.GetArg(acn.OptIpamQueryInterval).(int) stopcnm = acn.GetArg(acn.OptStopAzureVnet).(bool) vers := acn.GetArg(acn.OptVersion).(bool) + createDefaultExtNetworkType := acn.GetArg(acn.OptCreateDefaultExtNetworkType).(string) telemetryEnabled := acn.GetArg(acn.OptTelemetry).(bool) if vers { @@ -230,6 +239,17 @@ func main() { httpRestService.SetOption(acn.OptCnsURL, cnsURL) httpRestService.SetOption(acn.OptNetPluginPath, cniPath) httpRestService.SetOption(acn.OptNetPluginConfigFile, cniConfigFile) + httpRestService.SetOption(acn.OptCreateDefaultExtNetworkType, createDefaultExtNetworkType) + + // Create default ext network if commandline option is set + if len(strings.TrimSpace(createDefaultExtNetworkType)) > 0 { + if err := platform.CreateDefaultExtNetwork(createDefaultExtNetworkType); err == nil { + log.Printf("[Azure CNS] Successfully created default ext network") + } else { + log.Printf("[Azure CNS] Failed to create default ext network due to error: %v", err) + return + } + } // Start CNS. if httpRestService != nil { @@ -308,6 +328,14 @@ func main() { log.Printf("CNS Received unhandled error %v, shutting down.", err) } + if len(strings.TrimSpace(createDefaultExtNetworkType)) > 0 { + if err := platform.DeleteDefaultExtNetwork(); err == nil { + log.Printf("[Azure CNS] Successfully deleted default ext network") + } else { + log.Printf("[Azure CNS] Failed to delete default ext network due to error: %v", err) + } + } + // Cleanup. if httpRestService != nil { httpRestService.Stop() diff --git a/common/config.go b/common/config.go index 6254e4097c..89e86cc746 100644 --- a/common/config.go +++ b/common/config.go @@ -72,6 +72,10 @@ const ( OptTelemetryConfigDir = "telemetry-config-file" OptTelemetryConfigDirAlias = "d" + // Create ext Hns network + OptCreateDefaultExtNetworkType = "create-defaultextnetwork-type" + OptCreateDefaultExtNetworkTypeAlias = "defaultextnetworktype" + // Disable Telemetry OptTelemetry = "telemetry" OptTelemetryAlias = "dt" diff --git a/platform/os_linux.go b/platform/os_linux.go index a0009e2ec1..3fbd9e47e0 100644 --- a/platform/os_linux.go +++ b/platform/os_linux.go @@ -10,6 +10,7 @@ import ( "os/exec" "time" + "github.com/Azure/azure-container-networking/cns" "github.com/Azure/azure-container-networking/log" ) @@ -111,3 +112,28 @@ func KillProcessByName(processName string) error { func SetSdnRemoteArpMacAddress() error { return nil } + +// CreateDefaultExtNetwork creates the default ext network (if it doesn't exist already) +// to create external switch on windows platform. +// This is windows platform specific. +func CreateDefaultExtNetwork(networkType string) error { + return fmt.Errorf("CreateDefaultExtNetwork shouldn't be called for the platform: %s", GetOSInfo) +} + +// DeleteDefaultExtNetwork deletes the default HNS network. +// This is windows platform specific. +func DeleteDefaultExtNetwork() error { + return fmt.Errorf("DeleteDefaultExtNetwork shouldn't be called for the platform: %s", GetOSInfo) +} + +// CreateHnsNetwork creates the HNS network with the provided configuration +// This is windows platform specific. +func CreateHnsNetwork(nwConfig cns.CreateHnsNetworkRequest) error { + return fmt.Errorf("CreateHnsNetwork shouldn't be called for the platform: %s", GetOSInfo) +} + +// DeleteHnsNetwork deletes the HNS network with the provided name. +// This is windows platform specific. +func DeleteHnsNetwork(networkName string) error { + return fmt.Errorf("DeleteHnsNetwork shouldn't be called for the platform: %s", GetOSInfo) +} diff --git a/platform/os_windows.go b/platform/os_windows.go index 81ab05233b..79fc422911 100644 --- a/platform/os_windows.go +++ b/platform/os_windows.go @@ -5,12 +5,15 @@ package platform import ( "bytes" + "encoding/json" "fmt" "os/exec" "strings" "time" + "github.com/Azure/azure-container-networking/cns" "github.com/Azure/azure-container-networking/log" + "github.com/Microsoft/hcsshim" ) const ( @@ -50,6 +53,19 @@ const ( // Command to restart HNS service RestartHnsServiceCommand = "Restart-Service -Name hns" + + // Name of the external hns network + ExtHnsNetworkName = "ext" + + // Address prefix for external hns network + ExtHnsNetworkAddressPrefix = "192.168.255.0/30" + + // Gateway address for external hns network + ExtHnsNetworkGwAddress = "192.168.255.1" + + // HNS network types + hnsL2Bridge = "l2bridge" + hnsL2Tunnel = "l2tunnel" ) // Flag to check if sdnRemoteArpMacAddress registry key is set @@ -182,3 +198,132 @@ func SetSdnRemoteArpMacAddress() error { return nil } + +// CreateHnsNetwork creates the HNS network with the provided configuration +func CreateHnsNetwork(nwConfig cns.CreateHnsNetworkRequest) error { + log.Printf("[Azure CNS] CreateHnsNetwork") + // Initialize HNS network. + hnsNetwork := &hcsshim.HNSNetwork{ + Name: nwConfig.NetworkName, + Type: nwConfig.NetworkType, + NetworkAdapterName: nwConfig.NetworkAdapterName, + SourceMac: nwConfig.SourceMac, + DNSSuffix: nwConfig.DNSSuffix, + DNSServerList: nwConfig.DNSServerList, + DNSServerCompartment: nwConfig.DNSServerCompartment, + ManagementIP: nwConfig.ManagementIP, + AutomaticDNS: nwConfig.AutomaticDNS, + } + + for _, policy := range nwConfig.Policies { + hnsNetwork.Policies = append(hnsNetwork.Policies, policy) + } + + for _, subnet := range nwConfig.Subnets { + hnsSubnet := hcsshim.Subnet{ + AddressPrefix: subnet.AddressPrefix, + GatewayAddress: subnet.GatewayAddress, + } + + hnsNetwork.Subnets = append(hnsNetwork.Subnets, hnsSubnet) + } + + for _, macPool := range nwConfig.MacPools { + hnsMacPool := hcsshim.MacPool{ + StartMacAddress: macPool.StartMacAddress, + EndMacAddress: macPool.EndMacAddress, + } + hnsNetwork.MacPools = append(hnsNetwork.MacPools, hnsMacPool) + } + + return createHnsNetwork(hnsNetwork) +} + +// DeleteHnsNetwork deletes the HNS network with the provided name +func DeleteHnsNetwork(networkName string) error { + log.Printf("[Azure CNS] DeleteHnsNetwork") + + return deleteHnsNetwork(networkName) +} + +// CreateDefaultExtNetwork creates default HNS network named ext (if it doesn't exist already) +// to create external switch on windows platform. +// This allows orchestrators to start CNS which pre-provisions the network so that the +// VM network blip / disconnect is avoided when calling cni add for the very first time. +func CreateDefaultExtNetwork(networkType string) error { + networkType = strings.ToLower(strings.TrimSpace(networkType)) + if len(networkType) == 0 { + return nil + } + + if networkType != hnsL2Bridge && networkType != hnsL2Tunnel { + return fmt.Errorf("Invalid hns network type %s", networkType) + } + + log.Printf("[Azure CNS] CreateDefaultExtNetwork") + extHnsNetwork, _ := hcsshim.GetHNSNetworkByName(ExtHnsNetworkName) + + if extHnsNetwork != nil { + log.Printf("[Azure CNS] Found existing DefaultExtNetwork with type: %s", extHnsNetwork.Type) + if !strings.EqualFold(networkType, extHnsNetwork.Type) { + return fmt.Errorf("Network type mismatch with existing network: %s", extHnsNetwork.Type) + } + + return nil + } + + // create new hns network + log.Printf("[Azure CNS] Creating DefaultExtNetwork with type %s", networkType) + + hnsNetwork := &hcsshim.HNSNetwork{ + Name: ExtHnsNetworkName, + Type: networkType, + } + + hnsSubnet := hcsshim.Subnet{ + AddressPrefix: ExtHnsNetworkAddressPrefix, + GatewayAddress: ExtHnsNetworkGwAddress, + } + + hnsNetwork.Subnets = append(hnsNetwork.Subnets, hnsSubnet) + + return createHnsNetwork(hnsNetwork) +} + +// DeleteDefaultExtNetwork deletes the default HNS network +func DeleteDefaultExtNetwork() error { + log.Printf("[Azure CNS] DeleteDefaultExtNetwork") + + return deleteHnsNetwork(ExtHnsNetworkName) +} + +// createHnsNetwork calls the hcshim to create the hns network +func createHnsNetwork(hnsNetwork *hcsshim.HNSNetwork) error { + // Marshal the request. + buffer, err := json.Marshal(hnsNetwork) + if err != nil { + return err + } + hnsRequest := string(buffer) + + // Create the HNS network. + log.Printf("[Azure CNS] HNSNetworkRequest POST request:%+v", hnsRequest) + hnsResponse, err := hcsshim.HNSNetworkRequest("POST", "", hnsRequest) + log.Printf("[Azure CNS] HNSNetworkRequest POST response:%+v err:%v.", hnsResponse, err) + + return err +} + +// deleteHnsNetwork calls HNS to delete the network with the provided name +func deleteHnsNetwork(networkName string) error { + hnsNetwork, err := hcsshim.GetHNSNetworkByName(networkName) + if err == nil { + // Delete the HNS network. + var hnsResponse *hcsshim.HNSNetwork + log.Printf("[Azure CNS] HNSNetworkRequest DELETE id:%v", hnsNetwork.Id) + hnsResponse, err = hcsshim.HNSNetworkRequest("DELETE", hnsNetwork.Id, "") + log.Printf("[Azure CNS] HNSNetworkRequest DELETE response:%+v err:%v.", hnsResponse, err) + } + + return err +}