github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/maas/instance.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/gomaasapi" 12 "github.com/juju/names" 13 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/network" 16 "github.com/juju/juju/status" 17 "github.com/juju/juju/storage" 18 ) 19 20 type maasInstance interface { 21 instance.Instance 22 zone() (string, error) 23 hostname() (string, error) 24 hardwareCharacteristics() (*instance.HardwareCharacteristics, error) 25 volumes(names.MachineTag, []names.VolumeTag) ([]storage.Volume, []storage.VolumeAttachment, error) 26 } 27 28 type maas1Instance struct { 29 maasObject *gomaasapi.MAASObject 30 environ *maasEnviron 31 statusGetter func(instance.Id) (string, string) 32 } 33 34 var _ maasInstance = (*maas1Instance)(nil) 35 36 func (mi *maas1Instance) String() string { 37 hostname, err := mi.hostname() 38 if err != nil { 39 // This is meant to be impossible, but be paranoid. 40 hostname = fmt.Sprintf("<DNSName failed: %q>", err) 41 } 42 return fmt.Sprintf("%s:%s", hostname, mi.Id()) 43 } 44 45 func (mi *maas1Instance) Id() instance.Id { 46 return maasObjectId(mi.maasObject) 47 } 48 49 func maasObjectId(maasObject *gomaasapi.MAASObject) instance.Id { 50 // Use the node's 'resource_uri' value. 51 return instance.Id(maasObject.URI().String()) 52 } 53 54 func convertInstanceStatus(statusMsg, substatus string, id instance.Id) instance.InstanceStatus { 55 maasInstanceStatus := status.StatusEmpty 56 switch statusMsg { 57 case "": 58 logger.Debugf("unable to obtain status of instance %s", id) 59 statusMsg = "error in getting status" 60 case "Deployed": 61 maasInstanceStatus = status.StatusRunning 62 case "Deploying": 63 maasInstanceStatus = status.StatusAllocating 64 if substatus != "" { 65 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 66 } 67 case "Failed Deployment": 68 maasInstanceStatus = status.StatusProvisioningError 69 if substatus != "" { 70 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 71 } 72 default: 73 maasInstanceStatus = status.StatusEmpty 74 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 75 } 76 return instance.InstanceStatus{ 77 Status: maasInstanceStatus, 78 Message: statusMsg, 79 } 80 } 81 82 // Status returns a juju status based on the maas instance returned 83 // status message. 84 func (mi *maas1Instance) Status() instance.InstanceStatus { 85 statusMsg, substatus := mi.statusGetter(mi.Id()) 86 return convertInstanceStatus(statusMsg, substatus, mi.Id()) 87 } 88 89 func (mi *maas1Instance) Addresses() ([]network.Address, error) { 90 interfaceAddresses, err := mi.interfaceAddresses() 91 if err != nil { 92 return nil, errors.Annotate(err, "getting node interfaces") 93 } 94 95 logger.Debugf("instance %q has interface addresses: %+v", mi.Id(), interfaceAddresses) 96 return interfaceAddresses, nil 97 } 98 99 var refreshMAASObject = func(maasObject *gomaasapi.MAASObject) (gomaasapi.MAASObject, error) { 100 // Defined like this to allow patching in tests to overcome limitations of 101 // gomaasapi's test server. 102 return maasObject.Get() 103 } 104 105 // interfaceAddresses fetches a fresh copy of the node details from MAAS and 106 // extracts all addresses from the node's interfaces. Returns an error 107 // satisfying errors.IsNotSupported() if MAAS API does not report interfaces 108 // information. 109 func (mi *maas1Instance) interfaceAddresses() ([]network.Address, error) { 110 // Fetch a fresh copy of the instance JSON first. 111 obj, err := refreshMAASObject(mi.maasObject) 112 if err != nil { 113 return nil, errors.Annotate(err, "getting instance details") 114 } 115 116 subnetsMap, err := mi.environ.subnetToSpaceIds() 117 if err != nil { 118 return nil, errors.Trace(err) 119 } 120 // Get all the interface details and extract the addresses. 121 interfaces, err := maasObjectNetworkInterfaces(&obj, subnetsMap) 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 126 var addresses []network.Address 127 for _, iface := range interfaces { 128 if iface.Address.Value != "" { 129 logger.Debugf("found address %q on interface %q", iface.Address, iface.InterfaceName) 130 addresses = append(addresses, iface.Address) 131 } else { 132 logger.Infof("no address found on interface %q", iface.InterfaceName) 133 } 134 } 135 return addresses, nil 136 } 137 138 func (mi *maas1Instance) architecture() (arch, subarch string, err error) { 139 // MAAS may return an architecture of the form, for example, 140 // "amd64/generic"; we only care about the major part. 141 arch, err = mi.maasObject.GetField("architecture") 142 if err != nil { 143 return "", "", err 144 } 145 parts := strings.SplitN(arch, "/", 2) 146 arch = parts[0] 147 if len(parts) == 2 { 148 subarch = parts[1] 149 } 150 return arch, subarch, nil 151 } 152 153 func (mi *maas1Instance) zone() (string, error) { 154 // TODO (anastasiamac 2016-03-31) 155 // This code is needed until gomaasapi testing code is 156 // updated to align with MAAS. 157 // Currently, "zone" property is still treated as field 158 // by gomaasi infrastructure and is searched for 159 // using matchField(node, "zone", zoneName) instead of 160 // getMap. 161 // @see gomaasapi/testservice.go#findFreeNode 162 // bug https://bugs.launchpad.net/gomaasapi/+bug/1563631 163 zone, fieldErr := mi.maasObject.GetField("zone") 164 if fieldErr == nil && zone != "" { 165 return zone, nil 166 } 167 168 obj := mi.maasObject.GetMap()["zone"] 169 if obj.IsNil() { 170 return "", errors.New("zone property not set on maas") 171 } 172 zoneMap, err := obj.GetMap() 173 if err != nil { 174 return "", errors.New("zone property is not an expected type") 175 } 176 nameObj, ok := zoneMap["name"] 177 if !ok { 178 return "", errors.New("zone property is not set correctly: name is missing") 179 } 180 str, err := nameObj.GetString() 181 if err != nil { 182 return "", err 183 } 184 return str, nil 185 } 186 187 func (mi *maas1Instance) cpuCount() (uint64, error) { 188 count, err := mi.maasObject.GetMap()["cpu_count"].GetFloat64() 189 if err != nil { 190 return 0, err 191 } 192 return uint64(count), nil 193 } 194 195 func (mi *maas1Instance) memory() (uint64, error) { 196 mem, err := mi.maasObject.GetMap()["memory"].GetFloat64() 197 if err != nil { 198 return 0, err 199 } 200 return uint64(mem), nil 201 } 202 203 func (mi *maas1Instance) tagNames() ([]string, error) { 204 obj := mi.maasObject.GetMap()["tag_names"] 205 if obj.IsNil() { 206 return nil, errors.NotFoundf("tag_names") 207 } 208 array, err := obj.GetArray() 209 if err != nil { 210 return nil, err 211 } 212 tags := make([]string, len(array)) 213 for i, obj := range array { 214 tag, err := obj.GetString() 215 if err != nil { 216 return nil, err 217 } 218 tags[i] = tag 219 } 220 return tags, nil 221 } 222 223 func (mi *maas1Instance) hardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 224 nodeArch, _, err := mi.architecture() 225 if err != nil { 226 return nil, errors.Annotate(err, "error determining architecture") 227 } 228 nodeCpuCount, err := mi.cpuCount() 229 if err != nil { 230 return nil, errors.Annotate(err, "error determining cpu count") 231 } 232 nodeMemoryMB, err := mi.memory() 233 if err != nil { 234 return nil, errors.Annotate(err, "error determining available memory") 235 } 236 zone, err := mi.zone() 237 if err != nil { 238 return nil, errors.Annotate(err, "error determining availability zone") 239 } 240 hc := &instance.HardwareCharacteristics{ 241 Arch: &nodeArch, 242 CpuCores: &nodeCpuCount, 243 Mem: &nodeMemoryMB, 244 AvailabilityZone: &zone, 245 } 246 nodeTags, err := mi.tagNames() 247 if err != nil && !errors.IsNotFound(err) { 248 return nil, errors.Annotate(err, "error determining tag names") 249 } 250 if len(nodeTags) > 0 { 251 hc.Tags = &nodeTags 252 } 253 return hc, nil 254 } 255 256 func (mi *maas1Instance) hostname() (string, error) { 257 // A MAAS instance has its DNS name immediately. 258 return mi.maasObject.GetField("hostname") 259 } 260 261 // MAAS does not do firewalling so these port methods do nothing. 262 func (mi *maas1Instance) OpenPorts(machineId string, ports []network.PortRange) error { 263 logger.Debugf("unimplemented OpenPorts() called") 264 return nil 265 } 266 267 func (mi *maas1Instance) ClosePorts(machineId string, ports []network.PortRange) error { 268 logger.Debugf("unimplemented ClosePorts() called") 269 return nil 270 } 271 272 func (mi *maas1Instance) Ports(machineId string) ([]network.PortRange, error) { 273 logger.Debugf("unimplemented Ports() called") 274 return nil, nil 275 }