github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/linklayerdevices.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "runtime" 9 "strings" 10 11 "github.com/juju/errors" 12 jujutxn "github.com/juju/txn" 13 "gopkg.in/mgo.v2/bson" 14 "gopkg.in/mgo.v2/txn" 15 16 "github.com/juju/juju/network" 17 ) 18 19 // linkLayerDeviceDoc describes the persistent state of a link-layer network 20 // device for a machine. 21 type linkLayerDeviceDoc struct { 22 // DocID is the link-layer device global key, prefixed by ModelUUID. 23 DocID string `bson:"_id"` 24 25 // Name is the name of the network device as it appears on the machine. 26 Name string `bson:"name"` 27 28 // ModelUUID is the UUID of the model this device belongs to. 29 ModelUUID string `bson:"model-uuid"` 30 31 // MTU is the maximum transmission unit the device can handle. 32 MTU uint `bson:"mtu"` 33 34 // ProviderID is a provider-specific ID of the device, prefixed by 35 // ModelUUID. Empty when not supported by the provider. 36 ProviderID string `bson:"providerid,omitempty"` 37 38 // MachineID is the ID of the machine this device belongs to. 39 MachineID string `bson:"machine-id"` 40 41 // Type is the undelying type of the device. 42 Type LinkLayerDeviceType `bson:"type"` 43 44 // MACAddress is the media access control (MAC) address of the device. 45 MACAddress string `bson:"mac-address"` 46 47 // IsAutoStart is true if the device should be activated on boot. 48 IsAutoStart bool `bson:"is-auto-start"` 49 50 // IsUp is true when the device is up (enabled). 51 IsUp bool `bson:"is-up"` 52 53 // ParentName is the name of the parent device, which may be empty. When set 54 // the parent device must be on the same machine, unless the current device 55 // is inside a container, in which case ParentName can be a global key of a 56 // BridgeDevice on the host machine of the container. 57 ParentName string `bson:"parent-name"` 58 } 59 60 // LinkLayerDeviceType defines the type of a link-layer network device. 61 type LinkLayerDeviceType string 62 63 const ( 64 // LoopbackDevice is used for loopback devices. 65 LoopbackDevice LinkLayerDeviceType = "loopback" 66 67 // EthernetDevice is used for Ethernet (IEEE 802.3) devices. 68 EthernetDevice LinkLayerDeviceType = "ethernet" 69 70 // VLAN_8021QDevice is used for IEEE 802.1Q VLAN devices. 71 VLAN_8021QDevice LinkLayerDeviceType = "802.1q" 72 73 // BondDevice is used for bonding devices. 74 BondDevice LinkLayerDeviceType = "bond" 75 76 // BridgeDevice is used for OSI layer-2 bridge devices. 77 BridgeDevice LinkLayerDeviceType = "bridge" 78 ) 79 80 // IsValidLinkLayerDeviceType returns whether the given value is a valid 81 // link-layer network device type. 82 func IsValidLinkLayerDeviceType(value string) bool { 83 switch LinkLayerDeviceType(value) { 84 case LoopbackDevice, EthernetDevice, 85 VLAN_8021QDevice, 86 BondDevice, BridgeDevice: 87 return true 88 } 89 return false 90 } 91 92 // LinkLayerDevice represents the state of a link-layer network device for a 93 // machine. 94 type LinkLayerDevice struct { 95 st *State 96 doc linkLayerDeviceDoc 97 } 98 99 func newLinkLayerDevice(st *State, doc linkLayerDeviceDoc) *LinkLayerDevice { 100 return &LinkLayerDevice{st: st, doc: doc} 101 } 102 103 // DocID returns the globally unique ID of the link-layer device, including the 104 // model UUID as prefix. 105 func (dev *LinkLayerDevice) DocID() string { 106 return dev.st.docID(dev.doc.DocID) 107 } 108 109 // Name returns the name of the device, as it appears on the machine. 110 func (dev *LinkLayerDevice) Name() string { 111 return dev.doc.Name 112 } 113 114 // MTU returns the maximum transmission unit the device can handle. 115 func (dev *LinkLayerDevice) MTU() uint { 116 return dev.doc.MTU 117 } 118 119 // ProviderID returns the provider-specific device ID, if set. 120 func (dev *LinkLayerDevice) ProviderID() network.Id { 121 return network.Id(dev.localProviderID()) 122 } 123 124 func (dev *LinkLayerDevice) localProviderID() string { 125 return dev.st.localID(dev.doc.ProviderID) 126 } 127 128 // MachineID returns the ID of the machine this device belongs to. 129 func (dev *LinkLayerDevice) MachineID() string { 130 return dev.doc.MachineID 131 } 132 133 // Machine returns the Machine this device belongs to. 134 func (dev *LinkLayerDevice) Machine() (*Machine, error) { 135 return dev.st.Machine(dev.doc.MachineID) 136 } 137 138 // Type returns this device's underlying type. 139 func (dev *LinkLayerDevice) Type() LinkLayerDeviceType { 140 return dev.doc.Type 141 } 142 143 // MACAddress returns the media access control (MAC) address of the device. 144 func (dev *LinkLayerDevice) MACAddress() string { 145 return dev.doc.MACAddress 146 } 147 148 // IsAutoStart returns whether the device is set to automatically start on boot. 149 func (dev *LinkLayerDevice) IsAutoStart() bool { 150 return dev.doc.IsAutoStart 151 } 152 153 // IsUp returns whether the device is currently up. 154 func (dev *LinkLayerDevice) IsUp() bool { 155 return dev.doc.IsUp 156 } 157 158 // ParentName returns the name of this device's parent device, if set. The 159 // parent device is almost always on the same machine as the child device, but 160 // as a special case a child device on a container machine can have a parent 161 // BridgeDevice on the container's host machine. In the last case ParentName() 162 // returns the global key of the parent device, not just its name. 163 func (dev *LinkLayerDevice) ParentName() string { 164 return dev.doc.ParentName 165 } 166 167 func (dev *LinkLayerDevice) parentDeviceNameAndMachineID() (string, string) { 168 if dev.doc.ParentName == "" { 169 // No parent set, so no ID and name to return. 170 return "", "" 171 } 172 // In case ParentName is a global key, try getting the host machine ID from 173 // there first. 174 hostMachineID, parentDeviceName, err := parseLinkLayerDeviceParentNameAsGlobalKey(dev.doc.ParentName) 175 if err != nil { 176 // We validate the ParentName before setting it, so this case cannot 177 // happen and we're only logging the error. 178 logger.Errorf("%s has invalid parent: %v", dev, err) 179 return "", "" 180 } 181 if hostMachineID == "" { 182 // Parent device is on the same machine and ParentName is not a global 183 // key. 184 return dev.doc.ParentName, dev.doc.MachineID 185 } 186 return parentDeviceName, hostMachineID 187 } 188 189 // ParentDevice returns the LinkLayerDevice corresponding to the parent device 190 // of this device, if set. When no parent device name is set, it returns nil and 191 // no error. 192 func (dev *LinkLayerDevice) ParentDevice() (*LinkLayerDevice, error) { 193 if dev.doc.ParentName == "" { 194 return nil, nil 195 } 196 197 parentDeviceName, parentMachineID := dev.parentDeviceNameAndMachineID() 198 return dev.machineProxy(parentMachineID).LinkLayerDevice(parentDeviceName) 199 } 200 201 func (dev *LinkLayerDevice) parentDocID() string { 202 parentDeviceName, parentMachineID := dev.parentDeviceNameAndMachineID() 203 parentGlobalKey := linkLayerDeviceGlobalKey(parentMachineID, parentDeviceName) 204 if parentGlobalKey == "" { 205 return "" 206 } 207 return dev.st.docID(parentGlobalKey) 208 } 209 210 // machineProxy is a convenience wrapper for calling Machine.LinkLayerDevice() 211 // or Machine.forEachLinkLayerDeviceDoc() from a *LinkLayerDevice and machineID. 212 func (dev *LinkLayerDevice) machineProxy(machineID string) *Machine { 213 return &Machine{st: dev.st, doc: machineDoc{Id: machineID}} 214 } 215 216 // Remove removes the device, if it exists. No error is returned when the device 217 // was already removed. ErrParentDeviceHasChildren is returned if this device is 218 // a parent to one or more existing devices and therefore cannot be removed. 219 func (dev *LinkLayerDevice) Remove() (err error) { 220 defer errors.DeferredAnnotatef(&err, "cannot remove %s", dev) 221 222 buildTxn := func(attempt int) ([]txn.Op, error) { 223 if attempt > 0 { 224 if err = dev.errNoOperationsIfMissing(); err != nil { 225 return nil, err 226 } 227 } 228 return removeLinkLayerDeviceOps(dev.st, dev.DocID(), dev.parentDocID()) 229 } 230 return dev.st.run(buildTxn) 231 } 232 233 func (dev *LinkLayerDevice) errNoOperationsIfMissing() error { 234 _, err := dev.machineProxy(dev.doc.MachineID).LinkLayerDevice(dev.doc.Name) 235 if errors.IsNotFound(err) { 236 return jujutxn.ErrNoOperations 237 } else if err != nil { 238 return errors.Trace(err) 239 } 240 return nil 241 } 242 243 // removeLinkLayerDeviceOps returns the list of operations needed to remove the 244 // device with the given linkLayerDeviceDocID, asserting it still exists and has 245 // no children referring to it. If the device is a child, parentDeviceDocID will 246 // be non-empty and the operations includes decrementing the parent's 247 // NumChildren. 248 func removeLinkLayerDeviceOps(st *State, linkLayerDeviceDocID, parentDeviceDocID string) ([]txn.Op, error) { 249 var numChildren int 250 if parentDeviceDocID == "" { 251 // If not a child, verify it has no children. 252 var err error 253 numChildren, err = getParentDeviceNumChildrenRefs(st, linkLayerDeviceDocID) 254 if err != nil { 255 return nil, errors.Trace(err) 256 } 257 } 258 259 // We know the DocID has a valid format for a global key, hence the last 260 // return below is ignored. 261 machineID, deviceName, canBeGlobalKey := parseLinkLayerDeviceGlobalKey(linkLayerDeviceDocID) 262 if !canBeGlobalKey { 263 return nil, errors.Errorf( 264 "link-layer device %q on machine %q has unexpected key format", 265 machineID, deviceName, 266 ) 267 } 268 if numChildren > 0 { 269 return nil, newParentDeviceHasChildrenError(deviceName, numChildren) 270 } 271 272 var ops []txn.Op 273 if parentDeviceDocID != "" { 274 ops = append(ops, decrementDeviceNumChildrenOp(parentDeviceDocID)) 275 } 276 277 addressesQuery := findAddressesQuery(machineID, deviceName) 278 if addressesOps, err := st.removeMatchingIPAddressesDocOps(addressesQuery); err == nil { 279 ops = append(ops, addressesOps...) 280 } else { 281 return nil, errors.Trace(err) 282 } 283 284 return append(ops, 285 removeLinkLayerDeviceDocOp(linkLayerDeviceDocID), 286 removeLinkLayerDevicesRefsOp(linkLayerDeviceDocID), 287 ), nil 288 } 289 290 // removeLinkLayerDeviceDocOp returns an operation to remove the 291 // linkLayerDeviceDoc matching the given linkLayerDeviceDocID, asserting it 292 // still exists. 293 func removeLinkLayerDeviceDocOp(linkLayerDeviceDocID string) txn.Op { 294 return txn.Op{ 295 C: linkLayerDevicesC, 296 Id: linkLayerDeviceDocID, 297 Assert: txn.DocExists, 298 Remove: true, 299 } 300 } 301 302 // removeLinkLayerDeviceUnconditionallyOps returns the list of operations to 303 // unconditionally remove the device matching the given linkLayerDeviceDocID, 304 // along with its linkLayerDevicesRefsDoc. No asserts are included for the 305 // existence of both documents. 306 func removeLinkLayerDeviceUnconditionallyOps(linkLayerDeviceDocID string) []txn.Op { 307 // Reuse the regular remove ops, but drop their asserts. 308 removeDeviceDocOp := removeLinkLayerDeviceDocOp(linkLayerDeviceDocID) 309 removeDeviceDocOp.Assert = nil 310 removeRefsOp := removeLinkLayerDevicesRefsOp(linkLayerDeviceDocID) 311 removeRefsOp.Assert = nil 312 313 return []txn.Op{ 314 removeDeviceDocOp, 315 removeRefsOp, 316 } 317 } 318 319 // insertLinkLayerDeviceDocOp returns an operation inserting the given newDoc, 320 // asserting it does not exist yet. 321 func insertLinkLayerDeviceDocOp(newDoc *linkLayerDeviceDoc) txn.Op { 322 return txn.Op{ 323 C: linkLayerDevicesC, 324 Id: newDoc.DocID, 325 Assert: txn.DocMissing, 326 Insert: *newDoc, 327 } 328 } 329 330 // updateLinkLayerDeviceDocOp returns an operation updating the fields of 331 // existingDoc with the respective values of those fields in newDoc. DocID, 332 // ModelUUID, MachineID, and Name cannot be changed. ProviderID cannot be 333 // changed once set. In all other cases newDoc values overwrites existingDoc 334 // values. 335 func updateLinkLayerDeviceDocOp(existingDoc, newDoc *linkLayerDeviceDoc) txn.Op { 336 changes := make(bson.M) 337 if existingDoc.ProviderID == "" && newDoc.ProviderID != "" { 338 // Only allow changing the ProviderID if it was empty. 339 changes["providerid"] = newDoc.ProviderID 340 } 341 if existingDoc.Type != newDoc.Type { 342 changes["type"] = newDoc.Type 343 } 344 if existingDoc.MTU != newDoc.MTU { 345 changes["mtu"] = newDoc.MTU 346 } 347 if existingDoc.MACAddress != newDoc.MACAddress { 348 changes["mac-address"] = newDoc.MACAddress 349 } 350 if existingDoc.IsAutoStart != newDoc.IsAutoStart { 351 changes["is-auto-start"] = newDoc.IsAutoStart 352 } 353 if existingDoc.IsUp != newDoc.IsUp { 354 changes["is-up"] = newDoc.IsUp 355 } 356 if existingDoc.ParentName != newDoc.ParentName { 357 changes["parent-name"] = newDoc.ParentName 358 } 359 360 var updates bson.D 361 if len(changes) > 0 { 362 updates = append(updates, bson.DocElem{Name: "$set", Value: changes}) 363 } 364 365 return txn.Op{ 366 C: linkLayerDevicesC, 367 Id: existingDoc.DocID, 368 Assert: txn.DocExists, 369 Update: updates, 370 } 371 } 372 373 // assertLinkLayerDeviceExistsOp returns an operation asserting the document 374 // matching linkLayerDeviceDocID exists. 375 func assertLinkLayerDeviceExistsOp(linkLayerDeviceDocID string) txn.Op { 376 return txn.Op{ 377 C: linkLayerDevicesC, 378 Id: linkLayerDeviceDocID, 379 Assert: txn.DocExists, 380 } 381 } 382 383 // String returns a human-readable representation of the device. 384 func (dev *LinkLayerDevice) String() string { 385 return fmt.Sprintf("%s device %q on machine %q", dev.doc.Type, dev.doc.Name, dev.doc.MachineID) 386 } 387 388 func (dev *LinkLayerDevice) globalKey() string { 389 return linkLayerDeviceGlobalKey(dev.doc.MachineID, dev.doc.Name) 390 } 391 392 func linkLayerDeviceGlobalKey(machineID, deviceName string) string { 393 if machineID == "" || deviceName == "" { 394 return "" 395 } 396 return "m#" + machineID + "#d#" + deviceName 397 } 398 399 func parseLinkLayerDeviceGlobalKey(globalKey string) (machineID, deviceName string, canBeGlobalKey bool) { 400 if !strings.Contains(globalKey, "#") { 401 // Can't be a global key. 402 return "", "", false 403 } 404 keyParts := strings.Split(globalKey, "#") 405 if len(keyParts) != 4 || (keyParts[0] != "m" && keyParts[2] != "d") { 406 // Invalid global key format. 407 return "", "", true 408 } 409 machineID, deviceName = keyParts[1], keyParts[3] 410 return machineID, deviceName, true 411 } 412 413 // IsValidLinkLayerDeviceName returns whether the given name is a valid network 414 // link-layer device name, depending on the runtime.GOOS value. 415 func IsValidLinkLayerDeviceName(name string) bool { 416 if runtimeGOOS == "linux" { 417 return isValidLinuxDeviceName(name) 418 } 419 hasHash := strings.Contains(name, "#") 420 return !hasHash && stringLengthBetween(name, 1, 255) 421 } 422 423 // runtimeGOOS is defined to allow patching in tests. 424 var runtimeGOOS = runtime.GOOS 425 426 // isValidLinuxDeviceName returns whether the given deviceName is valid, 427 // using the same criteria as dev_valid_name(9) in the Linux kernel: 428 // - no whitespace allowed 429 // - length from 1 to 15 ASCII characters 430 // - literal "." and ".." as names are not allowed. 431 // Additionally, we don't allow "#" in the name. 432 func isValidLinuxDeviceName(name string) bool { 433 hasWhitespace := whitespaceReplacer.Replace(name) != name 434 isDot, isDoubleDot := name == ".", name == ".." 435 hasValidLength := stringLengthBetween(name, 1, 15) 436 hasHash := strings.Contains(name, "#") 437 438 return hasValidLength && !(hasHash || hasWhitespace || isDot || isDoubleDot) 439 } 440 441 // whitespaceReplacer strips whitespace characters from the input string. 442 var whitespaceReplacer = strings.NewReplacer( 443 " ", "", 444 "\t", "", 445 "\v", "", 446 "\n", "", 447 "\r", "", 448 ) 449 450 func stringLengthBetween(value string, minLength, maxLength uint) bool { 451 length := uint(len(value)) 452 return length >= minLength && length <= maxLength 453 } 454 455 // Addresses returns all IP addresses assigned to the device. 456 func (dev *LinkLayerDevice) Addresses() ([]*Address, error) { 457 var allAddresses []*Address 458 callbackFunc := func(resultDoc *ipAddressDoc) { 459 allAddresses = append(allAddresses, newIPAddress(dev.st, *resultDoc)) 460 } 461 462 findQuery := findAddressesQuery(dev.doc.MachineID, dev.doc.Name) 463 if err := dev.st.forEachIPAddressDoc(findQuery, nil, callbackFunc); err != nil { 464 return nil, errors.Trace(err) 465 } 466 return allAddresses, nil 467 } 468 469 // RemoveAddresses removes all IP addresses assigned to the device. 470 func (dev *LinkLayerDevice) RemoveAddresses() error { 471 findQuery := findAddressesQuery(dev.doc.MachineID, dev.doc.Name) 472 ops, err := dev.st.removeMatchingIPAddressesDocOps(findQuery) 473 if err != nil { 474 return errors.Trace(err) 475 } 476 477 return dev.st.runTransaction(ops) 478 }